diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index b157c5e6a..000000000 --- a/.editorconfig +++ /dev/null @@ -1,16 +0,0 @@ -# http://editorconfig.org -root = true - -[*] -indent_style = tab -indent_size = 1 -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[*.{xml,yml}] -indent_size = 2 - -[*.{md,yml}] -trim_trailing_whitespace = false diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..1db1cce22 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,30 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Provide as much of a code sample as possible. Unirest is pretty small so it you should be able to mostly copy-paste into the issue +1. do **NOT** paste authentication or client keys into the issue +1. provide full stack traces if possible + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Environmental Data:** + - Java Version + - Version [e.g. 3.3.03] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..bbcbbe7d6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 000000000..c10ab213b --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,149 @@ +# Copilot Instructions for Unirest-Java + +## Project Overview +Unirest is a simplified, lightweight HTTP client library for Java. This is a multi-module Maven project requiring Java 11+. + +### Module Structure +- **unirest** (unirest-java-core): Core HTTP client library +- **unirest-modules-gson**: GSON JSON object mapper integration +- **unirest-modules-jackson**: Jackson 3.x JSON object mapper integration +- **unirest-modules-jackson-legacy**: Jackson 2.x JSON object mapper integration +- **unirest-modules-mocks**: Mock client for testing Unirest-based code +- **unirest-bdd-tests**: Behavioral/integration tests +- **unirest-java-bom**: Bill of Materials for dependency management + +## Code Style & Standards + +### What Copilot Should NOT Comment On (Already Enforced by Checkstyle) +The following are automatically checked by checkstyle during the build. Do not flag these in PR reviews: +- **Naming conventions**: Constants, local variables, member names, method names, package names, parameters, static variables, and type names +- **Import issues**: Illegal imports (sun.* packages), redundant imports, unused imports +- **Line length**: Max 186 characters +- **File length**: Max 1100 lines +- **Method length**: Max 35 lines +- **Method count**: Max 50 public methods per class +- **Parameter count**: Max 5 parameters per method +- **Nested if depth**: Max 3 levels +- **Braces requirement**: All control structures require braces +- **Hidden fields**: Except in setters and constructor parameters +- **Long literals**: Must use uppercase 'L' suffix +- **Forbidden patterns**: + - `Calendar.getInstance` (use Java 8+ DateTime API) + - `System.out.println` + - `.printStackTrace()` + +### License Headers +All Java source files must include the MIT license header. This is enforced by maven-license-plugin. + +### Coding Conventions +- Use Java 8+ DateTime API instead of `Calendar` +- Proper exception handling (no printStackTrace) +- No direct console output (System.out) +- Prefer fluent/builder patterns (see `Config`, `HttpRequest` classes) +- Use `Optional` for nullable return values +- Follow immutable patterns where practical + +## Testing Requirements + +### Test Structure +All contributions must include tests: + +1. **Unit Tests** (`unirest/src/test/java/`) + - Test individual components in isolation + - Use JUnit 5 with Mockito for mocking + - Use AssertJ for assertions + +2. **Behavioral Tests** (`unirest-bdd-tests/src/test/java/BehaviorTests/`) + - Integration tests against a mock server + - Tests extend `BddTest` base class + - Mock server auto-resets between tests + - Test real HTTP scenarios end-to-end + +### Testing Frameworks Used +- JUnit 5 (junit-jupiter) +- Mockito for mocking +- AssertJ for fluent assertions +- JSONAssert for JSON comparisons +- Custom `MockClient` from unirest-modules-mocks + +### Example Test Pattern (Unit Test) +```java +@ExtendWith(MockitoExtension.class) +class MyFeatureTest { + @InjectMocks + private MyClass underTest; + + @Test + void shouldDoSomething() { + // Given / When / Then pattern + } +} +``` + +### Example Test Pattern (Behavioral Test) +```java +class MyFeatureTest extends BddTest { + @Test + void canDoSomethingOverHttp() { + Unirest.config().someConfiguration(); + + var response = Unirest.get(MockServer.GET) + .asObject(RequestCapture.class); + + assertEquals(200, response.getStatus()); + } +} +``` + +## Architecture Guidelines + +### Key Classes +- `Unirest`: Static entry point with primary instance +- `UnirestInstance`: Configurable instance (supports multiple instances) +- `Config`: Configuration holder with fluent API +- `HttpRequest`/`BaseRequest`: Request builder interface and implementation +- `HttpResponse`: Response wrapper with various body types +- `Client`: HTTP client abstraction (JavaClient implementation) +- `ObjectMapper`: JSON serialization abstraction + +### Adding New Features +1. Add configuration option to `Config` class if needed +2. Implement feature in appropriate module +3. Add unit tests in same module +4. Add behavioral tests in `unirest-bdd-tests` +5. Update documentation in `mkdocs/docs/` if user-facing + +## Build & Verification + +### Running Tests +```bash +mvn verify +``` + +### Build Commands +```bash +mvn clean install # Full build with tests +mvn package -DskipTests # Build without tests +``` + +### Code Coverage +JaCoCo enforces minimum 60% code coverage on complexity. + +## Pull Request Guidelines +- Maintain backwards compatibility - this is critical for the project +- All tests must pass (`mvn verify`) +- Checkstyle must pass (runs automatically during compile) +- Include both unit and behavioral tests for new features +- Update CHANGELOG.md for user-facing changes +- Update documentation in mkdocs/docs/ for new features + +## Dependencies +- Dependencies are managed in the parent pom.xml +- Test dependencies are inherited: JUnit 5, Mockito, AssertJ, Guava (test scope) +- Prefer existing dependencies over adding new ones +- All production dependencies should be carefully considered for size impact + +## Documentation +- API documentation via Javadoc +- User documentation in `mkdocs/docs/` +- Example code should be tested (reference behavioral tests) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml new file mode 100644 index 000000000..56db88a53 --- /dev/null +++ b/.github/workflows/documentation.yml @@ -0,0 +1,30 @@ +name: documentation +on: + push: + branches: + - master + - main +permissions: + contents: write +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Configure Git Credentials + run: | + git config user.name github-actions[bot] + git config user.email 41898282+github-actions[bot]@users.noreply.github.com + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV + - uses: actions/cache@v4 + with: + key: mkdocs-material-${{ env.cache_id }} + path: .cache + restore-keys: | + mkdocs-material- + - run: pip install mkdocs-material + - working-directory: ./mkdocs + run: mkdocs gh-deploy --force \ No newline at end of file diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 000000000..e2c9def3f --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,35 @@ +name: Verify + +on: + push: + # Sequence of patterns matched against refs/heads + branches-ignore: + - apache5 + pull_request: + +permissions: + contents: read + +jobs: + build: + timeout-minutes: 15 + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - name: Set up JDK 25 + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '25' + - name: Cache + uses: actions/cache@v4 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: Build with Maven + run: mvn -B package --file pom.xml + diff --git a/.gitignore b/.gitignore index ed3c04537..3b265261c 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,15 @@ target .DS_Store .settings release.properties -META-INF .idea *.iml +.gitconfig +_site/ +Gemfile.lock +.sass-cache/ +.jekyll-metadata +1.jpg +dependency-reduced-pom.xml +site +``` diff --git a/.java-version b/.java-version new file mode 100644 index 000000000..7273c0fa8 --- /dev/null +++ b/.java-version @@ -0,0 +1 @@ +25 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 8b5c5222b..000000000 --- a/.travis.yml +++ /dev/null @@ -1,10 +0,0 @@ -language: java - -jdk: - - openjdk6 - - openjdk7 - - oraclejdk7 - - oraclejdk8 - -os: - - linux diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..e7c310b9c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,751 @@ +## 4.10.2 +* Encode URL-encoded form field names with the configured charset, matching form values. Callers that pre-encoded names to work around this defect should pass the original unencoded name instead. + +## 4.10.1 +* Add support for the new QUERY method in HTTP +* Minor javadoc and error message fixes + +## 4.10.0 +* Issue #567 : Add support for additional headers added to multi-part form parts. This is supported though a new sub-builder for fields. This sub-builder will eventually take over for many of the complex overloads of ::field which will be dropped in Unirest 5. + +## 4.9.0 +* Issue #531 : Support chaining expectations in the mocking library. Users who had multiple matching expects for a route will now be expected to be performed in order. + +## 4.8.1 +* Dependency updates + +## 4.8.0 +* Changed default ordering of form fields to the order they were added from alphabetical. Added additional sort method in case anyone wants them sorted the old way. + +## 4.7.4 +* Re-Introduce a 2 Jackson 2 module available as unirest-modules-jackson-legacy. This will give people a better chance to migrate. This module will be dropped if/when Unirest goes to version 5. See the discussions for an apology. + +## 4.7.0 +* Bump Jackson module to Jackson 3. This also requires the Jackson module to be at Java 17. All other modules are still at 11 + * Users who wish to continue to use Jackson 2 can use the unirest-modules-jackson-legacy module that was added just after this version (see 4.7.4). + +## 4.6.0 +* Add support for multiple proxies and authentication per host + +## 4.5.2 +* Dependency updates + +## 4.5.1 +* Add Config method for setting system property jdk.internal.httpclient.disableHostnameVerification. This will disable host name verification for ALL instances of Unirest or the Java client on the JVM + +## 4.5.0 +* Added support for Server Sent Events. See Documentation. + +## 4.4.11 +* Updated dependencies +* versions 4.4.8 - 4.4.10 were lost to Maven Central authentication changes + +## 4.4.7 +* issue #542 should pass default response to dynamically created invocations + +## 4.4.5 +* Issue #536 UnirestInstance should implement AutoCloseable + +## 4.4.4 +* issue #528 reset content headers when multiPartContent is called + +## 4.4.0 +* Issue #526 split connection timeout from request timeout. The request no longer has a connection timeout setting, and instead has a request timeout setting as a replacement. Previously these two settings had been conflated. The overall config also has a default request timeout that will be applied to all requests if the request setting is not set. The default setting is null which indicates a infinite timeout. + +## 4.3.2 +* Issue #523 when paging create a new copy of the request for each page rather than reusing the original +* Issue #524 support the Partitioned flag on cookies for CHIPs (Cookies Having Independent Partitioned State) +* Make Path public. Because its handy + +## 4.3.1 +* Added convenience methods for using ContentType with accepts and contentType methods + +## 4.3.0 +The modules have been repackaged and put into new maven coordinates in order to avoid conflicts with the 3.x line of unirest. +The mock module has had its maven artifact ID changed only. + +| Old Maven Artifact ID | New Maven Artifact ID | Old Class Package | New Class Package | +|----|-------------------------|----------------------|------------------------------| +|unirest-object-mappers-gson | unirest-modules-gson | kong.unirest.gson | kong.unirest.modules.gson | +|unirest-objectmapper-jackson | unirest-modules-jackson | kong.unirest.jackson | kong.unirest.modules.jackson | +|unirest-mocks | unirest-modules-mocks | kong.unirest.core | kong.unirest.core | + + +## 4.2.9 +* re-work request summary to be closer in reality to the actual request for multipart requests. + +## 4.2.8 +* populate the content type for non-binary multiparts +* added method to accept a form part with a ContentType type in addition to string + +## 4.2.7 +* Changes to the json CoreFactory to allow for multiple different strategies for finding the JsonEngine in the classpath. +* Allow setting a custom JsonFactory directly + +## 4.2.6 +* Allow for per-request config of http version + +## 4.2.5 +* Minor optimizations and doc updates +* Issue #511: pass the HTTP version from the config to the request + +## 4.2.3 +* issue #506 have thenConsume pass on a basic response with no body so that other things like retry logic and interceptors can function properly + +## 4.2.2 +* Unirest-Mocks: add some options for doing verify by adding a 'Times' param for things like never() and exactly(number) +* In 429/503 retry logic skip retry when response is null + +## 4.2.1 +* #503 Remove old retry setting that no longer works with the Java client +* #504 allow asserting just one expectation verification on mock asserts + +## 4.1.1 +* issue #493: Copy status text from MockResponse +* MockClient not properly evaluating verb in matching invokes +* Expose a way in MockClient to supply a handler supplier for responses based dynamically on the request. Great for creating TestDouble services. + +## 4.1.0 +* No longer supporting shaded jars. Core Unirest no longer has any dependencies at all, and the modular framework of the library no longer jives with the shade-plugin causing other issues. + +## 4.0.12 +* Expose headers on RequestSummary + +## 4.0.11 +* Support retry on async requests + +## 4.0.10 +* Java TTL is in seconds not millies + +## 4.0.9 +* Expose a RetryStrategy to allow more advanced overrides of retry-behavior + * Allow client to determine IF the request should be retries + * Allow clients to determine the wait time + + +## 4.0.8 +* Add 503 to the list of default retryable codes +* Sets the Java TTL when configured + +## 4.0.6 - 4.0.7 (RIP) +* Eaten by maven central + +## 4.0.5 +* Issue #493 Make MockClient take a MockResponse properly +* Documentation and upgrade guide fixes + +## 4.0.4 +* Issue #484 Support headers with null values + +## 4.0.3 +* Another tweek to the BOM +* Update dependencies (Jackson, GSON, Junit/Mockito) + +## 4.0.2 +* Fix BOM coordinates + +## 4.0.0 +* Java 11+ is now a requirement +* HTTP2 support +* WebSocket support +* Apache Replaced by native Java HTTP Client +* Modular Maven config with bom +* Zero external runtime dependencies by default +* You MUST include JSON support on your own ([see upgrade guide](mkdocs/docs/upgrade-guide)) +* New Maven coordinates +* New packaging in order to not conflict with older versions + +## 3.14.5 +* fix issue introduced with 3.14.4 where binary data is not read + +## 3.14.4 +* Fix NullPointerException in MockClient +* Fix argument validation in MonitoringInputStream + +## 3.14.3 +* Attempt to skip recovering the body from large binary responses like files when an error occurs. + +## 3.14.2 +* issue #469, do not add bytes twice in MonitoringInputStream + +## 3.14.1 +* Add the RequestSummary to the HttpResponse + +## 3.14.0 +* issue #461 return HttpResponse for asEmpty() +* issue #450 add authenticated proxies to request +* issue #451 pass original failure reason to new mapped response in ifFailure +* issue #444, add ability to directly assert a body (only works with non-multipart bodies +* issue #444, add ability to directly assert a body field in a multipart request +* re-name some methods on the Assert interface to better express a fluent feel + +## 3.13.13 +* Cookie dates always follow US Locale to avoid invalid unicode in headers + +## 3.13.12 +* Allow use of progress monitor for byte[] bodies +* Dependency upgrades + +## 3.13.11 +* Content-Type should be overwritten rather than adding a second Content-Type +* Forcing multipart overrides content-type headers + +## 3.13.9 +* Bypass silly NPE in Json Parsing + +## 3.13.8 +* Made HttpMethod::all static + +## 3.13.7 +* Dependency upgrades + +## 3.13.6 +* issue #424 Cannot use response.mapError with ByteResponse + +## 3.13.5 +* Bump httpasyncclient dependency to 4.1.5 for [bug fixes](http://www.apache.org/dist/httpcomponents/httpasyncclient/RELEASE_NOTES-4.1.x.txt). + +## 3.13.4 +* Add ability to override all Headers with a map + +## 3.13.3 +* Support a way to override Apache HttpClientBuilder options with the Client Builder. All Unirest configs are set first, then the consumer is called which allows consumers to override or add additional configs: +```java + Unirest.config() + .httpClient(ApacheClient.builder(c -> c.setMaxConnTotal(5000)); +``` + +## 3.13.2 +* Allow using a MockResponse in the MockRequestBuilder + +## 3.13.1 +* add some new features to MockClient + * mockClient.reset() will clear any expectations + * mockClient.defaultResponse() returns a default response expectation for when an explicit expectation was not matched + * thenReturn(Supplier supplier) allows you to set the response body as a supplier to be invoked at request time. + +## 3.13.0 +* Support InputStreams as bodies. +* Support ProgressMonitors for InputStream bodies +* Unirest-Mocks now includes a MockResponse and a MockConfig for use independent of the MockClient + +## 3.12.0 +* Bump GSON to 2.8.8 +* Support honoring Retry-After headers on 429/529 for regular (not async) requests. + * This feature will likely not make it to async until Unirest 4. + * Can be enabled with ```Unirest.config().retryAfter(true);``` + * Has a max re-try counter with a default of 10 which can be set with: + * ```Unirest.config().retryAfter(true, maxNumberOfRetries);``` + * đŸ”„ While Honoring The Retry-After header the thread will be blocked! đŸ”„ + * It is highly recommend that this feature be used in conjunction with a circuit-breaking framework. + * Let's say you have a web app that is making Unirest calls to downstream system X. You have many requests invoking this same service. If X starts to return 429's and Unirest is waiting on ALL of those requests. Unirest will quickly consume all your threads. At this time Unirest has no circuit-breaker of it's own to detect that this is happening. It will simply be happy to pause all of your threads forever. + +## 3.11.13 +* Bump Jackson version in object-mapper-jackson to 2.12.4 +* Bump test and CI dependencies + +## 3.11.12 +* make the default basic cache concurrent. + +## 3.11.11 +* useSystemProperties should be reset back to false on reset + +## 3.11.10 +* issue #394 use the configured Object Mapper rather than always Json +* internal pre-factorings to get ready for Unirest 4 + +## 3.11.09 +* Expected body param values for Mock expects need to be url encoded +* Support ANY expectation on methods for MockClient. (e.g. ```expect(HttpMethod.GET)``) + +## 3.11.08 +* Adds new body matchers to the Mock client for asserting multipart forms. + +## 3.11.07 +* add a new object constructor on JSONObject for basic object serialization +* issue #392 overwrite non-specified number serialization + +## 3.11.06 +* add a convenience method for setting the content type +* add a common reference to popular mime types +* cache methods on Config were not returning the config for the builder pattern. +* add a CookieSpecs const class for reference + +## 3.11.05 +* issue #383 some problems with relocated packages. + +## 3.11.04 +* issue #383 missing some relocations for uber-jars +* PR #381 support for custom ciphers and protocols for https requests +* Mock server should call into metrics just like the real thing +* CI/CD improvements + +## 3.11.03 +* issue #378 make getDefaultBaseUrl public +* issue #376 make sane toString representations for body parts. +* issue #376 add a method for getting a particular body field +* issue #376 added a ```asString()``` to the request summary to get string of the request so far suitable for logging +* issue #379 Interceptors are not called when using MockClient + +## 3.11.02 +* Issue #373 MockClient should pass the config to the response. +* Issue #374 Add methods to add default cookies to the config +* Issue #375 Do not Escape HTML in JSON + +## 3.11.01 +* Upgrade Apache Http Client to 4.5.13 (fixes incorrect handling of malformed authority component in request URIs.) +* Upgrade Jackson to 2.11.3 for the Jackson object mapper module +* Upgrade various test and ci dependencies + +## 3.11.00 +* issue #368 honor hosts header when set by consumer. This mimics behavior in Postman +* issue #370 expose copyOptions for file downloads +* issue #265 New Mocking framework! See the unirest-mocks module. +* issue #305 adding a noCharset() method as a more obvious way to do noCharset(null) +* Added Documentation +* Merged the request ```body(JSONObject)``` amd ```body(JSONArray)``` into common ```body(JSONElement)``` method +* Filled in some missing methods on RequestBodyEntity from HttpRequestWithBody to fully allow chaining in different orders. + +## 3.10.00 +* Introduce new HttpStatus constants class for reference to Http response codes. +* Switch the ApacheClient to use Apache's build in eviction monitor rather than a custom one. +* Add native default support for various Java DateTime types for the built in object mapper. All types will serialize to ISO-8601. Parsing from json will attempt various ISO variants. + * Types Supported: + * ZonedDateTime + * LocalDateTime + * LocalDate + * Calendar + * Date + +## 3.9.00 +* Issue #362 when passing a string to the body method for a post, route as a string rather than passing to the object mapper. +* Issue #362 when passing a native Unirest JSON Type to the body method for a post, route as a Json Type rather than passing to the object mapper. +* issue #363 support multiple interceptors + +## 3.8.06 +* issue #359 make the default object mapper lazy so gson can be excluded + +### 3.8.01-.05 (RIP) + * sacrificed to an angry Nexus god. + * Nexus deploy plugin upgraded + +## 3.8.00 +* Minor updates to org.apache.httpcomponents to 4.5.12 +* Upgrade Jackson to 2.11.0 +* Switch all unit tests to JUnit 5 and update Mockito +* issue #358 add ability to set a collection of cookies +* issue #325 add response caching framework + +## 3.7.04 +* issue #357 Nulls are not serialized on JSONElement::toString + +## 3.7.03 +* fix defaultBaseUrl to return the config builder + +## 3.7.02 +* Issue #348 Add 'Duration' as flavor to configure connection TTL +* Issue #350 Parsing error occurs when POST response is empty gzip content type + +## 3.7.01 +* Issue #345 better error for path segment missing in JSONPointer +* Support ```mapError``` to map into a String + +## 3.7.00 +* Issue #342: Add a default base URL configuration + +## 3.6.01 +* Issue #341: + * Do not URLDecode cookie values because they may not be and decoding can remove legit values like + + * Split cookie pairs on the first = only so values can have = + +## 3.6.00 +* issue #336 Add ProgressMonitor for file downloads. + +## 3.5.00 +* Re-package the object mapper sub-modules to work with Java 11 per issue #324. +* Update Jackson to 2.10.2 +* Update various build tools to work with Java 11 + +## 3.4.04 +* issue #335 keep around failure bodies for mapping to error objects +* Request objects implement equals for future feature to support request caching + +## 3.4.03 +* Patch bump of all Apache dependencies +* Patch bump of gson +* Minor bump of test dependencies: junit, mockito, etc + +## 3.4.02 +* #333 Spaces in route parameters +* added the SameSite cookie attribute to the cookie parser +* Updated checkstyle test dependency for security + +## 3.4.01 +* #331 ArrayIndexOutOfBoundsException when parsing cookie +* Handle quoted cookie values + +## 3.4.00 +* Add methods to add cookies to the request and read cookie from the response + +## 3.3.00 +* Allow setting a custom HostNameVerifier for issue #322 +* By default use DefaultHostNameVerifier rather than the noop one + +## 3.2.00 +* Allow users to inject a custom SSLContext into the Config for security +* Allow for a custom interceptor that is called when + * Before the request + * After the request + * When a fatal connection error happens. +* Directly exposing Apache Interceptors is now deprecated +* #319 escape spaces and tabs in raw urls +* Override toString on Headers for better logging pr #321 + +## 3.1.02 +* #308 When parsing an error body allow for non-parsing error bodies + +## 3.1.00 +* #301 Unirest is now configured by default with a JsonObjectMapper +* #302 Support a globally configured error consumer +* JsonNode now has a ```toPrettyString``` method for getting a formatted json string + +## 3.0.00 +* Replace the dependency on org.json with a native kong.unirest library powered by gson that matches org.json interfaces. See the [Upgrade Guide](mkdocs/docs/upgrade-guide) for details. +* Issue #299. Remove gzip content-encoding header after decompression on async client + +## 2.4.01 +* #308 When parsing an error body allow for non-parsing error bodies + +## 2.4.00 +* add an entire new return type: ```asBytes()``` (as well as async versions) will return a raw byte[] array. + +## 2.3.17 +* Issue #292: Use per request Object Mapper for request bodies. + +## 2.3.16 +* Catch the proper error rather than Exception in the old-apache compatibility fix + +## 2.3.15 +* Update jackson-databind to 2.9.9.3 because .2 has shenanigans + +## 2.3.14 +* Update jackson-databind to 2.9.9.2 to address moderate security vulnerability + +## 2.3.13 +* Add ```FAIL_ON_UNKNOWN_PROPERTIES = false``` to the default Jackson object mapper. + +## 2.3.12 +* Remember when an older version of apache was on the path for the remainder of the runtime + +## 2.3.11 +* Skip calling apache method that may not exist if older versions of client are on the path to avoid MethodNotFound exceptions. +* Add a config option for setting the TTL of persistent connections:/ issue #286 + +## 2.3.10 +* Update of jackson-databind for CVE-2019-12814 + +## 2.3.09 +* #284 allow setting a custom factory for the RequestConfig + +## 2.3.08 +* #282 force maven not to pick the async clients version of client + +## 2.3.07 +* #280 regular Apache client not picking up max connections + +## 2.3.06 +* Updated dependencies to latest, including Apache Http Client (4.5.9) + +## 2.3.05 +* Unriest is now packaged both as a traditional jar and as a standalone jar with all of it's dependencies shaded. This is nice when you have a large project where many components may use different versions of things like Apache Http Client. + +## 2.3.02 +* Remove mistaken dependency that should have been scoped to test. + +## 2.3.01 +* Update Jackson dependency in object-mapper-jackson due to security. + +## 2.3.00 +* #267 make sure hooks are registered regardless of where we are in the lifecycle of the client engines +* #264 Adds a metric hook for observing the health and performance of unirest. +* #270 Throw a more helpful error when a older version of Apache Client has been pulled in over Unirest's requirement. +* Upgrade Apache Http Client Dependency to 4.5.8 + +## 2.2.02 +* Add a mapper function to transform a HttpResponse to a HttpResponse +```java +HttpResponse response = Unirest.get("http://someplace/number") + .asString() + .map(Integer::valueOf); +``` +* Add a few ways to map a error response into a different type than the original +```java +ErrorThing error = Unirest.get("http://someplace") + .asObject(Thing.class) + .mapError(ErrorThing.class); // will be null if the request had been successful + +Unirest.get("http://someplace") + .asObject(Thing.class) + .ifFailure(ErrorThing.class, f -> { + // f is a HttpResponse + // this function is not called if the request was succesful + }); + +``` + +## 2.2.01 +* add cookiespec policies to config. These are the same as Apache Http Clients cookiespec. See org.apache.http.client.config.CookieSpec + +## 2.2.00 +* Introduce default implementations of popular object mappers + * Jackson + * GSON + +## 2.1.03 +* Finally address #26 You can add a file upload progress monitor. This monitor will get called for each file in a multipart file upload by name. +``` java + Unirest.post("http://someplace") + .field("myFile", new File("/file/somthing.tar") + .uploadMonitor((fieldName, fileName, bytesWritten, totalBytes) -> { + // draw a upload progress bar or something + }) + .asEmpty() +``` +* Fix an issue where when using generic methods that take object, passing in complex objects like InputStreams would not get send as the correct type. + +## 2.1.02 +* #120 support client certificates. You can pass in an entire keystore or just the path to it in the config. +* part of #260: only support a single basic auth header. + +## 2.1.01 +* #260 add option to configure a default basic auth header + +## 2.1.00 +* #259 optionally flag the config to auto register the clients with shutdownhooks. +* #165 allow forcing a simple url-encoded param POST/PUT to be multipart. This adds a new method to the body interface so bumping the minor. + +## 2.0.05 +* #118 enable overriding socket and connection timeouts per request +* #72 enable passing a proxy per request. Only works with simple proxies for now. + +## 2.0.04 +* Disable SSL validation with ```Unirest.config().verifySsl(false)```. PLEASE DO NOT DO THIS IN PRODUCTION +* Disable Automatic retries with ```Unirest.config().ÏautomaticRetries(false)``` + +## 2.0.03 +* Make sure the GzipInputStream gets closed. +* Support disabling compressed results which is asked for by default +```java +Unirest.config().requestCompression(false); +``` +## 2.0.02 +* Pass route params as a map +```java +Unirest.get("http://example") + .routeParam(ImmutableMap.of("cheese", "cheddar", "age", 42)) +``` + +# The following were releases from the merged OpenUnirest project + +### OpenUnirest:3.3.05 +* Add a noop response type when you just don't care about the body. +```java + HttpResponse re = Unirest.get("http://no.body.knows").asEmpty(); +``` + +### OpenUnirest:3.3.04 +* Add the ability to follow a paged response by providing a lambda for the response type and one for getting the next page. It will stop once the next link is null or empty. +* The PagedList has handy dandy methods to pass consumers to all success and failure responses. +```java +PagedList result = Unirest.get("http://and.pagey") + .asPaged( + r -> r.asJson(), + r -> r.getHeaders().getFirst("nextPage") + ); +``` + + +### OpenUnirest:3.3.03 +* Pulled isSuccess() up to the response interface so it can be used and enjoyed by everyone. + +### OpenUnirest:3.3.02 +* Add the ability to make requests to non-standard web methods for things like WebDAV, SVN, or other horrible things. +```java + Unirest.request("CHEESE", "http://some.cheesy.thing") + .asString(); +``` + +### OpenUnirest:3.3.00 +* Slight breaking change with regard to accessing Apache specific classes via the config + * getClient and getAsyncClient return the Unirest containers for clients. You can still get to the Apache client through that for now but this is also deprecated. Eventually Unirest will make Apache just one of several modules. + * Apache client wrappers are now package local. +* Add support for overriding the default expected encoding both on a per-request basis and as a default in the config. +```java +// Changing the default from UTF-8 for all requests +// Unirest will still honor content-encoding if defined in the response headers +Unirest.config().setDefaultResponseEncoding("windows-1250"); + +// Changing the default on a request. +// This will explicitly be used EVEN IF the headers define something else. +Unirest.get("http://some.file.with.windows.encoding/file.txt") + .responseEncoding("windows-1250") + .asString(); +``` + +### OpenUnirest:3.2.06 +* access headers in order + +### OpenUnirest:3.2.05 +* UnirestInstance is autoclosable + +### OpenUnirest:3.2.04 +* Adds chainable ifSuccess and ifFailure consumer methods to the response. Now you can handle the response like +```java + Unirest.get("https://localhost/somewhere") + .asString() + .ifSuccess(r -> log.info("Yippy!")) + .ifFailure(r -> log.error("Booo")); +``` + +### OpenUnirest:3.2.03 +* Allow the configuration of header suppliers. +```java + Unirest.config().setDefaultHeader("trace", () -> value); +``` + +### OpenUnirest:3.2.02 +* distinguish between set and add for default headers. + +### OpenUnirest:3.2.01 +* add method to replace a header rather than append to it. + +### OpenUnirest:3.3.00 +* Now you can stream the results into a file! + * It doesn't need to be a file either. It could be any result. Unirest will shove it in a file. +```java +File file = Unirest.get("https://someplace/file.tar.gz") + .asFile("/local/storage/file.tar.gz") + .getBody(); +``` + +### OpenUnirest:3.3.00 +* When encountering a parsing error with asObject or asJson capture the body in a UnirestParsingException +* New BETA feature asFile method to stream the response into a file. + +### OpenUnirest:3.1.01 +* Detect if the async client has stopped for some reason and construct a new one. This one may be different from the one that was originally configured so we need to add a way to configure a supplier for clients rather than a direct client. + +### OpenUnirest:3.3.00 +* Deprecate methods that expose Apache. In the 4 line we will start supporting other clients. Primarily the java one supplied in Java9 (apache will still exist for 8-) +* Add several functional methods for dealing with the raw response before the connection is closed. This is nice for large responses. + +### OpenUnirest:3.0.04 +* Parsing handler should capture unirest exceptions just like other exceptions. + +### OpenUnirest:3.0.03 +* Support Java system properties for proxies via ```Unirest.config().useSystemProperties(true);``` + * https://docs.oracle.com/javase/7/docs/api/java/net/doc-files/net-properties.html + +### OpenUnirest:3.0.02 +* Add support for the authenticated proxies to the async client + +### OpenUnirest:3.0.01 +* Support for authenticated proxies with ```Unirest.config().proxy("proxy.server.host", 80, "username","password")``` + +### OpenUnirest:3.3.00 +* This is a **major** release with several **breaking changes** which (other than the namespace change) should ONLY impact you if you are using some of Unirests more advanced features or custom configurations. +* The maven artifact has changed to ```open-unirest-java``` +* The namespace has been shortened to just **unirest** (inspired by Java Spark) +* The configuration system has been completely redone. Previously it was scattered across several classes and was entirely static. The new system has instances and supports multiple configurations. See the UPGRADE_GUIDE.md for details. + * Start accessing the config with ```Unirest.config()``` +* Almost everything is now fronted by interfaces, this makes testing easier. +* Unirest itself can now be accessed as a interface with UnirestInstance +* ObjectMappers can be passed in as part of the builder. +* option no longer supports body (per http spec) +* Accept and ContentType are now 1st class headers in the builder (```Unirest.get(path).accept("application/json")```) +* Major internal fixes so that which builder operations were available when is consistent. + +### OpenUnirest:2.5.03 +* Lazy init the HttpClients so they don't get in the way of setting custom clients. + +### OpenUnirest:2.5.02 +* More safety for issue #41 + +### OpenUnirest:2.5.01 +* Fix Issue #41: possible init error in HttpClient under heavy load + +### OpenUnirest:2.5.00 +* Extracted Interface for HttpResponse to make testing easier + +### OpenUnirest:2.4.02 +* Updated org.json dependency + +### OpenUnirest:2.4.01 +* Add async versions of generic type methods + +### OpenUnirest:2.4.00 +* Issue #19 Add support for generic types with object mappers. + +### OpenUnirest:2.3.00 +* Add support for the JSON Patch standard (RFC6902) https://tools.ietf.org/html/rfc6902 +```java + Unirest.jsonPatch(MockServer.PATCH) + .add("/fruits/-", "Apple") + .remove("/bugs") + .replace("/lastname", "Flintsone") + .test("/firstname", "Fred") + .move("/old/location", "/new/location") + .copy("/original/location", "/new/location") + .asJson(); +``` + +### OpenUnirest:2.2.12 +* Add optional flag overload to ```Unirest.shutDown(false)```. The flag indicates if the various Options should be cleared. This only applies to options that could survive a shutdown. The HttpClients and thread monitors will still be discarded. + +### OpenUnirest:2.2.11 +* Change default MultiPart mode to BROWSER_COMPATIBLE in order to support unicode filenames. Clients wishing to use the legacy mode can set it to STRICT. This should not be a problem for most users as few servers today lack support for unicode file names. Issue #35 + +### OpenUnirest:2.2.10 +* Update Apache dependencies https://archive.apache.org/dist/httpcomponents/httpclient/RELEASE_NOTES-4.5.x.txt + +### OpenUnirest:2.2.08 + * add an option to disable cookie management with ```Options.enableCookieManagement(false)```. + * In the future ignoring cookies will be the default. + +### OpenUnirest:2.2.07 +* Fix init error + +### OpenUnirest:2.2.06 +* added ability to turn off redirect following with ```Options.followRedirects(boolean enable)``` (default is true) + +### OpenUnirest:2.2.04 +* Add ```.charset(Charset charset)``` to POSTS (both form and body) +* Clean up some ambiguous methods in form posting. This may be a breaking change for a very small number of users. There are better methods for handling these cases. +* Added the ability to add HttpRequestInterceptors to the client. Though ``` Options.addInterceptor(new TestInterceptor()); ``` + +### OpenUnirest:2.2.03 +* Expose the ```Options.init()``` method to restore the system to a fresh start after a shutdown. +* ```Unirest.shutdown()``` no longer throws a checked exception + +### OpenUnirest:2.2.02 +* Fix NPE with null bodies from HEAD requests. + +### OpenUnirest:2.2.01 +* Quitly consume and close InputStreams that may not be complete. + +### OpenUnirest:2.2.00 +* Major refactoring of how response objects are built. This internalizes HttpClientHelper which was previously public but not needed if you were using the library in in the expected way. This refactoring sets up the library for future work to extend the number of supported formats and for greater expression in the methods for those formats. +* Additional of functional map methods to HttpResponse so you don't have to stop the flow for further transformations. +* Update the org.json dependency +* Fixed issue with achor hashes in URLs (#17) +* Fixed NPE in passing args as maps (#20) + +### OpenUnirest:2.1.01 +* JSON parsing errors no longer just throw out of ```asJson``` but are captured and can be inspected via ```HttpResponse::getParsingError```. This resolves the fact that most API's do not return valid JSON for non-200 status codes. +* Return CompletableFuture rather that boring old Futures for all async methods. + + +### OpenUnirest:2.0.02 +* UnirestException is no longer checked +* Namespaces have been migrated to new **io.gitgub.openunirest** namespace +* Thread leak in Options fixed diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..ae6a4bb21 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at ryan.bergman@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..2ff83eb16 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,34 @@ +# Contributing to Unirest + +:+1::tada: First off, thanks for taking the time to contribute! :tada::+1: + +## Code of Conduct + +This project and everyone participating in it is governed by the [Open Unirest Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. + +## How Can I Contribute? + +### Reporting Bugs +**Explain how to produce the problem** in as many details as possible. When listing steps, **don't just say what you did, but explain how you did it**. +* **Provide specific examples to demonstrate the steps**. Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples. If you're providing snippets in the issue, use [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines). +* **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior. +* **Explain which behavior you expected to see instead and why.** + +Include details about your configuration and environment: + +* **What version of Unirest were you using?** +* **What version of Java were you running it on?** +* **Did you do any custom configuration to the client?** + + +### Contributing Code + +* Before you put in all the work to add a new feature. Try opening a issue and checking if it's something that will be accepted. Sometimes a feature you think might be useful might be contrary to the goals or direction of the project. We won't want folks to get frustrated, so take a minute and talk it through with us. +* If we do all agree it's a good idea we would expect: + * Backwards compatibility is important to this project and you should go out of your way to not make breaking changes. + * All contributions should be issued as pull requests and get reviewed by the admins of this project. + * All code should follow style guidelines enforced by the checkstyle and other static analysis. + * All code should have tests. + * Unit tests + * Behavioral tests (see https://github.com/Kong/unirest-java/tree/main/unirest-bdd-tests/src/test/java/BehaviorTests) + * You should have run verify before submitting and the PR needs to have passed TravisCI before being merged. diff --git a/LICENSE b/LICENSE index 78fe11e6c..68781a6f2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2013-2015 Mashape (https://www.mashape.com) + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013-2019. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/README.md b/README.md index 829398f93..93f9e53db 100644 --- a/README.md +++ b/README.md @@ -1,365 +1,21 @@ -# Unirest for Java [![Build Status][travis-image]][travis-url] +# Unirest for Java -![][unirest-logo] +[![Actions Status](https://github.com/kong/unirest-java/workflows/Verify/badge.svg)](https://github.com/kong/unirest-java/actions) +![Maven Central Version](https://img.shields.io/maven-central/v/com.konghq/unirest-java-bom) -[Unirest](http://unirest.io) is a set of lightweight HTTP libraries available in multiple languages, built and maintained by [Mashape](https://github.com/Mashape), who also maintain the open-source API Gateway [Kong](https://github.com/Mashape/kong). +## Unirest 4 +Unirest 4 is build on modern Java standards, and as such requires at least Java 11. -Do yourself a favor, and start making HTTP requests like this: +Unirest 4's dependencies are fully modular, and have been moved to new Maven coordinates to avoid conflicts with the previous versions. +You can use a maven bom to manage the modules: -```java -Unirest.post("http://httpbin.org/post") - .queryString("name", "Mark") - .field("last", "Polo") - .asJson() -``` +## Documentation +Our [Documentation](http://kong.github.io/unirest-java/) -[![License][license-image]][license-url] | -[![version][maven-version]][maven-url] | -[![Gitter][gitter-image]][gitter-url] +## Upgrading from Previous Versions +See the [Upgrade Guide](http://kong.github.io/unirest-java/upgrade-guide) +## ChangeLog +See the [Change Log](CHANGELOG.md) for recent changes. -## Features - -* Make `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS` requests -* Both syncronous and asynchronous (non-blocking) requests -* It supports form parameters, file uploads and custom body entities -* Easily add route parameters without ugly string concatenations -* Supports gzip -* Supports Basic Authentication natively -* Customizable timeout, concurrency levels and proxy settings -* Customizable default headers for every request (DRY) -* Customizable `HttpClient` and `HttpAsyncClient` implementation -* Automatic JSON parsing into a native object for JSON responses -* Customizable binding, with mapping from response body to java Object - -## Installing -Is easy as pie. Kidding. It's about as easy as doing these little steps: - -### With Maven - -You can use Maven by including the library: - -```xml - - com.mashape.unirest - unirest-java - 1.4.9 - -``` - -There are dependencies for Unirest-Java, these should be already installed, and they are as follows: - -```xml - - org.apache.httpcomponents - httpclient - 4.3.6 - - - org.apache.httpcomponents - httpasyncclient - 4.0.2 - - - org.apache.httpcomponents - httpmime - 4.3.6 - - - org.json - json - 20140107 - -``` - -If you would like to run tests, also add the following dependency along with the others: - -```xml - - junit - junit - 4.11 - test - - - commons-io - commons-io - 2.4 - test - -``` - -### Without Maven - -Alternatively if you don't use Maven, you can directly include the JAR file in the classpath: http://oss.sonatype.org/content/repositories/releases/com/mashape/unirest/unirest-java/1.4.9/unirest-java-1.4.9.jar - -Don't forget to also install the dependencies ([`org.json`](http://www.json.org/java/), [`httpclient 4.3.6`](http://hc.apache.org/downloads.cgi), [`httpmime 4.3.6`](http://hc.apache.org/downloads.cgi), [`httpasyncclient 4.0.2`](http://hc.apache.org/downloads.cgi)) in the classpath too. - -There is also a way to generate a Unirest-Java JAR file that already includes the required dependencies, but you will need Maven to generate it. Follow the instructions at http://blog.mashape.com/post/69117323931/installing-unirest-java-with-the-maven-assembly-plugin - -## Creating Request -So you're probably wondering how using Unirest makes creating requests in Java easier, here is a basic POST request that will explain everything: - -```java -HttpResponse jsonResponse = Unirest.post("http://httpbin.org/post") - .header("accept", "application/json") - .queryString("apiKey", "123") - .field("parameter", "value") - .field("foo", "bar") - .asJson(); -``` - -Requests are made when `as[Type]()` is invoked, possible types include `Json`, `Binary`, `String`, `Object`. - -If the request supports and it is of type `HttpRequestWithBody`, a body it can be passed along with `.body(String|JsonNode|Object)`. For using `.body(Object)` some pre-configuration is needed (see below). - -If you already have a map of parameters or do not wish to use seperate field methods for each one there is a `.fields(Map fields)` method that will serialize each key - value to form parameters on your request. - -`.headers(Map headers)` is also supported in replacement of multiple header methods. - -## Serialization -Before an `asObject(Class)` or a `.body(Object)` invokation, is necessary to provide a custom implementation of the `ObjectMapper` interface. -This should be done only the first time, as the instance of the ObjectMapper will be shared globally. - -For example, serializing Json from\to Object using the popular Jackson ObjectMapper takes only few lines of code. - -```java -// Only one time -Unirest.setObjectMapper(new ObjectMapper() { - private com.fasterxml.jackson.databind.ObjectMapper jacksonObjectMapper - = new com.fasterxml.jackson.databind.ObjectMapper(); - - public T readValue(String value, Class valueType) { - try { - return jacksonObjectMapper.readValue(value, valueType); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - public String writeValue(Object value) { - try { - return jacksonObjectMapper.writeValueAsString(value); - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - } -}); - -// Response to Object -HttpResponse bookResponse = Unirest.get("http://httpbin.org/books/1").asObject(Book.class); -Book bookObject = bookResponse.getBody(); - -HttpResponse authorResponse = Unirest.get("http://httpbin.org/books/{id}/author") - .routeParam("id", bookObject.getId()) - .asObject(Author.class); - -Author authorObject = authorResponse.getBody(); - -// Object to Json -HttpResponse postResponse = Unirest.post("http://httpbin.org/authors/post") - .header("accept", "application/json") - .header("Content-Type", "application/json") - .body(authorObject) - .asJson(); -``` - -### Route Parameters - -Sometimes you want to add dynamic parameters in the URL, you can easily do that by adding a placeholder in the URL, and then by setting the route parameters with the `routeParam` function, like: - -```java -Unirest.get("http://httpbin.org/{method}") - .routeParam("method", "get") - .queryString("name", "Mark") - .asJson(); -``` -In the example above the final URL will be `http://httpbin.org/get` - Basically the placeholder `{method}` will be replaced with `get`. - -The placeholder's format is as easy as: `{custom_name}` - -## Asynchronous Requests -Sometimes, well most of the time, you want your application to be asynchronous and not block, Unirest supports this in Java using anonymous callbacks, or direct method placement: - -```java -Future> future = Unirest.post("http://httpbin.org/post") - .header("accept", "application/json") - .field("param1", "value1") - .field("param2", "value2") - .asJsonAsync(new Callback() { - - public void failed(UnirestException e) { - System.out.println("The request has failed"); - } - - public void completed(HttpResponse response) { - int code = response.getStatus(); - Map headers = response.getHeaders(); - JsonNode body = response.getBody(); - InputStream rawBody = response.getRawBody(); - } - - public void cancelled() { - System.out.println("The request has been cancelled"); - } - -}); -``` - -## File Uploads -Creating `multipart` requests with Java is trivial, simply pass along a `File` or an InputStream Object as a field: - -```java -HttpResponse jsonResponse = Unirest.post("http://httpbin.org/post") - .header("accept", "application/json") - .field("parameter", "value") - .field("file", new File("/tmp/file")) - .asJson(); -``` - -## Custom Entity Body - -```java -HttpResponse jsonResponse = Unirest.post("http://httpbin.org/post") - .header("accept", "application/json") - .body("{\"parameter\":\"value\", \"foo\":\"bar\"}") - .asJson(); -``` - -## Byte Stream as Entity Body - -```java -final InputStream stream = new FileInputStream(new File(getClass().getResource("/image.jpg").toURI())); -final byte[] bytes = new byte[stream.available()]; -stream.read(bytes); -stream.close(); -final HttpResponse jsonResponse = Unirest.post("http://httpbin.org/post") - .field("name", "Mark") - .field("file", bytes, "image.jpg") - .asJson(); -``` - -## InputStream as Entity Body - -```java -HttpResponse jsonResponse = Unirest.post("http://httpbin.org/post") - .field("name", "Mark") - .field("file", new FileInputStream(new File(getClass().getResource("/image.jpg").toURI())), ContentType.APPLICATION_OCTET_STREAM, "image.jpg") - .asJson(); -``` - -## Basic Authentication -Authenticating the request with basic authentication can be done by calling the `basicAuth(username, password)` function: -```java -HttpResponse response = Unirest.get("http://httpbin.org/headers").basicAuth("username", "password").asJson(); -``` - -# Request - -The Java Unirest library follows the builder style conventions. You start building your request by creating a `HttpRequest` object using one of the following: - -```java -GetRequest request = Unirest.get(String url); -GetRequest request = Unirest.head(String url); -HttpRequestWithBody request = Unirest.post(String url); -HttpRequestWithBody request = Unirest.put(String url); -HttpRequestWithBody request = Unirest.patch(String url); -HttpRequestWithBody request = Unirest.options(String url); -HttpRequestWithBody request = Unirest.delete(String url); -``` - -# Response - -Upon recieving a response Unirest returns the result in the form of an Object, this object should always have the same keys for each language regarding to the response details. - -- `.getStatus()` - HTTP Response Status Code (Example: 200) -- `.getStatusText()` - HTTP Response Status Text (Example: "OK") -- `.getHeaders()` - HTTP Response Headers -- `.getBody()` - Parsed response body where applicable, for example JSON responses are parsed to Objects / Associative Arrays. -- `.getRawBody()` - Un-parsed response body - -# Advanced Configuration - -You can set some advanced configuration to tune Unirest-Java: - -### Custom HTTP clients - -You can explicitly set your own `HttpClient` and `HttpAsyncClient` implementations by using the following methods: - -```java -Unirest.setHttpClient(httpClient); -Unirest.setAsyncHttpClient(asyncHttpClient); -``` -### Timeouts - -You can set custom connection and socket timeout values (in **milliseconds**): - -```java -Unirest.setTimeouts(long connectionTimeout, long socketTimeout); -``` - -By default the connection timeout (the time it takes to connect to a server) is `10000`, and the socket timeout (the time it takes to receive data) is `60000`. You can set any of these timeouts to zero to disable the timeout. - -### Default Request Headers - -You can set default headers that will be sent on every request: - -```java -Unirest.setDefaultHeader("Header1", "Value1"); -Unirest.setDefaultHeader("Header2", "Value2"); -``` - -You can clear the default headers anytime with: - -```java -Unirest.clearDefaultHeaders(); -``` - -### Concurrency - -You can set custom concurrency levels if you need to tune the performance of the syncronous or asyncronous client: - -```java -Unirest.setConcurrency(int maxTotal, int maxPerRoute); -``` - -By default the maxTotal (overall connection limit in the pool) is `200`, and the maxPerRoute (connection limit per target host) is `20`. - -### Proxy - -You can set a proxy by invoking: - -```java -Unirest.setProxy(new HttpHost("127.0.0.1", 8000)); -``` - -# Exiting an application - -Unirest starts a background event loop and your Java application won't be able to exit until you manually shutdown all the threads by invoking: - -```java -Unirest.shutdown(); -``` - ----- - -Made with ♥ from the [Mashape](https://www.mashape.com/) team - -[unirest-logo]: http://cl.ly/image/2P373Y090s2O/Image%202015-10-12%20at%209.48.06%20PM.png - - -[license-url]: https://github.com/Mashape/unirest-java/blob/master/LICENSE -[license-image]: https://img.shields.io/badge/license-MIT-blue.svg?style=flat - -[gitter-url]: https://gitter.im/Mashape/unirest-java -[gitter-image]: https://img.shields.io/badge/Gitter-Join%20Chat-blue.svg?style=flat - -[travis-url]: https://travis-ci.org/Mashape/unirest-java -[travis-image]: https://img.shields.io/travis/Mashape/unirest-java.svg?style=flat - -[maven-url]: http://search.maven.org/#browse%7C1262490619 -[maven-version]: https://img.shields.io/maven-central/v/com.mashape.unirest/unirest-java.svg?style=flat - -[versioneye-url]: https://www.versioneye.com/user/projects/54b83a12050646ca5c0001fc -[versioneye-image]: https://www.versioneye.com/user/projects/54b83a12050646ca5c0001fc/badge.svg?style=flat diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..6d6e1882c --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,15 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +| ------- | ------------------ | +| 3.1.x | :white_check_mark: | +| 3.0.x | :x: | +| 2.4.x | :white_check_mark: | +| < 2.4 | :x: | +| 1.x.x | :x: | + +## Reporting a Vulnerability + +Please report an issue and we will respond to security issues asap diff --git a/bad-maven-test/pom.xml b/bad-maven-test/pom.xml new file mode 100644 index 000000000..b8851d814 --- /dev/null +++ b/bad-maven-test/pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + com.kong + bad-maven-test + 2.3.00-SNAPSHOT + Errors that happen when people are using crusty old version of apache Http client + + + + org.apache.maven.plugins + maven-compiler-plugin + + 11 + 11 + + + + + + + + com.konghq + unirest-java + 2.3.05 + standalone + + + \ No newline at end of file diff --git a/bad-maven-test/src/test/java/kong/BadMavenConfigTest.java b/bad-maven-test/src/test/java/kong/BadMavenConfigTest.java new file mode 100644 index 000000000..5a5471ca7 --- /dev/null +++ b/bad-maven-test/src/test/java/kong/BadMavenConfigTest.java @@ -0,0 +1,37 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong; + + +import kong.unirest.Unirest; + +public class BadMavenConfigTest { + + + public static void main(String[] args) { + Unirest.get("https://konghq.com").asString().ifSuccess(e -> System.out.println("e = " + e.getBody())); + } +} \ No newline at end of file diff --git a/build/checkstyle.xml b/build/checkstyle.xml new file mode 100644 index 000000000..492bca3b4 --- /dev/null +++ b/build/checkstyle.xml @@ -0,0 +1,243 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/mashape/unirest/http/HttpMethod.java b/build/fileHeader.txt similarity index 81% rename from src/main/java/com/mashape/unirest/http/HttpMethod.java rename to build/fileHeader.txt index b6e201055..ba1a912a3 100644 --- a/src/main/java/com/mashape/unirest/http/HttpMethod.java +++ b/build/fileHeader.txt @@ -1,7 +1,6 @@ -/* The MIT License -Copyright (c) 2013 Mashape (http://mashape.com) +Copyright for portions of unirest-java are held by Kong Inc (c) 2013. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -20,11 +19,4 @@ a copy of this software and associated documentation files (the NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.mashape.unirest.http; - -public enum HttpMethod { - GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS; -} +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/build/suppressions.xml b/build/suppressions.xml new file mode 100644 index 000000000..d7a0b2d08 --- /dev/null +++ b/build/suppressions.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/deploy b/deploy deleted file mode 100755 index a4207e769..000000000 --- a/deploy +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -mvn clean deploy -mvn release:clean -git commit -a -m "version bump" -git push origin master -mvn release:prepare -mvn release:perform - -# Note: Be sure that the local Maven settings.xml file is configured with the right credentials, and that the system has the right GPG keys installed (along with a valid GPG installation) - -# Now go to https://oss.sonatype.org/index.html and release it diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 000000000..ddb60c43f --- /dev/null +++ b/deploy.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# +# The MIT License +# +# Copyright for portions of unirest-java are held by Kong Inc (c) 2013. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + + +mvn clean verify -P ossrh +mvn release:prepare -DskipTests -P ossrh +mvn release:perform -DskipTests -P ossrh + +# Get the latest tag created by maven-release-plugin +TAG_NAME=$(git describe --tags --abbrev=0) + +gh release create "${TAG_NAME}" \ + --title "Release ${TAG_NAME}" \ + --notes "Release ${TAG_NAME}" \ + --verify-tag \ No newline at end of file diff --git a/formatter/unirest-code-format.xml b/formatter/unirest-code-format.xml deleted file mode 100644 index b532293e8..000000000 --- a/formatter/unirest-code-format.xml +++ /dev/null @@ -1,295 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/mkdocs/docs/caching.md b/mkdocs/docs/caching.md new file mode 100644 index 000000000..059276c89 --- /dev/null +++ b/mkdocs/docs/caching.md @@ -0,0 +1,54 @@ + +# Caching +Unirest offers a simple im memory response caching mechanism with a few options for entry expiration. +This can be either be enabled with defaults, with expiration options or consumers may supply a custom cache backed by the cache of their choice. +It is reccomended that in high load systems consumers back the cache with a dedicated cache implementation like EHCache or Guava. + +#### Basic cache: +```java + Unirest.config().cacheResponses(true); + + //These 1st response will be cached in this case: + Unirest.get("https://somwhere").asString(); + Unirest.get("https://somwhere").asString(); +``` +#### Advanced Options: +You can use a builder to customize eviction rules: + +```java + Unirest.config().cacheResponses(builder() + .depth(5) // Depth is the max number of entries cached + .maxAge(5, TimeUnit.MINUTES)); // Max age is how long the entry will be kept. +``` + +#### Custom Caches +You can also supply a custom cache by implementing the Cache Interface +```java + public static void main(String[] args){ + Unirest.config().cacheResponses(Cache.builder().backingCache(new GuavaCache())); + } + + // Example backing cache using Guava + public static class GuavaCache implements Cache { + com.google.common.cache.Cache regular = CacheBuilder.newBuilder().build(); + com.google.common.cache.Cache async = CacheBuilder.newBuilder().build(); + @Override + public HttpResponse get(Key key, Supplier> fetcher) { + try { + return regular.get(key, fetcher::get); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } + } + + @Override + public CompletableFuture getAsync(Key key, Supplier>> fetcher) { + try { + return async.get(key, fetcher::get); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } + } + } + +``` \ No newline at end of file diff --git a/mkdocs/docs/configuration.md b/mkdocs/docs/configuration.md new file mode 100644 index 000000000..460ec8665 --- /dev/null +++ b/mkdocs/docs/configuration.md @@ -0,0 +1,103 @@ +# Configuration +Previous versions of unirest had configuration split across several different places. Sometimes it was done on ```Unirest```, sometimes it was done on ```Option```, sometimes it was somewhere else. +All configuration is now done through ```Unirest.config()``` + + +```java + Unirest.config() + .connectTimeout(1000) + .proxy("proxy.example.com", 8080) + .setDefaultHeader("Accept", "application/json") + .followRedirects(false) + .enableCookieManagement(false) + .interceptor(new MyCustomInterceptor()); +``` + +Changing Unirest's config should ideally be done once, or rarely. Once Unirest has been activated configuration options that are involved in creating the client cannot be changed without an explicit shutdown or reset. + + + +## Config Options + +| Builder Method | Impact | Default | +|--------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------| +| ```clientCertificateStore(String,String)``` | Add a PKCS12 KeyStore by path for doing client certificates | | +| ```clientCertificateStore(KeyStore,String)``` | Add a PKCS12 KeyStore for doing client certificates | | +| ```connectTimeout(int)``` | Sets the connection timeout for all requests in millis | 10000 | +| ```connectionTTL(Duration)``` | Add total time to live (TTL) by [Duration](https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html). Good for moderns Java APIs. | -1 | +| ```connectionTTL(long,TimeUnit)``` | Total time to live (TTL) defines maximum life span of persistent connections regardless of their expiration setting. No persistent connection will be re-used past its TTL value. | -1 | +| ```cookieSpec(String)``` | set a cookie policy. Acceptable values: 'default' (same as Netscape), 'netscape', 'ignoreCookies', 'standard' (RFC 6265 interoprability profile) , 'standard-strict' (RFC 6265 strict profile) | default | +| ```defaultBaseUrl(String value)``` | Set a default base URL to be used for all requests that do not already contain a scheme | | +| ```disableHostNameVerification(Boolean value)``` | Sets the system property jdk.internal.httpclient.disableHostnameVerification. This will disable host name verification for ALL instances of Unirest or the Java client on the JVM | | +| ```setDefaultHeader(String, String)``` | Sets a default header. Will overwrite if it exists | | +| ```setDefaultHeader(String, Supplier)``` | Sets a default header by supplier. Good for setting trace tokens for microservice architectures. Will overwrite if it exists | | +| ```addDefaultHeader(String, String)``` | Adds a default header. Multiple for the same name can exist | | +| ```addDefaultHeader(String, Supplier)``` | Add a default header by supplier. Good for setting trace tokens for microservice architectures. | | +| ```setDefaultBasicAuth(String, String)``` | Add a default Basic Auth Header | | +| ```enableCookieManagement(boolean)``` | toggle accepting and storing cookies | true | +| ```errorHandler(Consumer> consumer)``` | Set a global error handler that will be invoked for any status > 400 or a parsing error | | +| ```followRedirects(boolean)``` | toggle following redirects | true | +| ```interceptor(Interceptor value)``` | Set a global Interceptor handler that will be invoked before and after each request | | +| ```proxy(proxy)``` | Sets a proxy object for negotiating proxy servers. Can include auth credentials | | +| ```requestTimeout(int)``` | Sets the request timeout for all requests in millis | none (infinite) | +| ```retryAfter(boolean)``` | Automatically retry synchronous requests on 429/529 responses with the Retry-After response header | false | +| ```retryAfter(boolean,int)``` | Automatically retry synchronous requests on 429/529 responses with the Retry-After response header as well as number of attempts | false | +| ```verifySsl(boolean)``` | toggle enforcing SSL | true | + +## Global Interceptor +You can set a global interceptor for your configuration. This is invoked before and after each request. +This can be useful for logging or injecting common attributes. + +See [Interceptor.java](https://github.com/Kong/unirest-java/blob/main/unirest/src/main/java/kong/unirest/core/Interceptor.java) for details. + + +## Multiple Configurations +As usual, Unirest maintains a primary single instance. Sometimes you might want different configurations for different systems. You might also want an instance rather than a static context for testing purposes. + +```java + + // this returns the same instance used by Unirest.get("http://somewhere/") + UnirestInstance unirest = Unirest.primaryInstance(); + // It can be configured and used just like the static context + unirest.config().connectTimeout(5000); + String result = unirest.get("http://foo").asString().getBody(); + + // You can also get a whole new instance + UnirestInstance unirest = Unirest.spawnInstance(); +``` + +## Object Mappers +Unirest offers a few different Object Mapper's based on popular JSON libraries (Jackson and GSON). +```xml + + com.konghq + unirest-modules-jackson + + + + com.konghq + unirest-modules-gson + +``` + +If you have some other need you can supply your own Object mapper by implementing the ```ObjectMapper``` interface. It has only a few methods + +## Metrics +Unirest has hooks for collecting metrics on your runtime code. This is a simple and lightweight framework that marks two events: + 1. The moment just before the actual request is made + 1. The moment just after the actual request is made + +Context information like method and request path are given to you so that you can collect based on whatever your needs are. +In its simplest form it might look like this: + +```java + Unirest.config().instrumentWith(requestSummary -> { + long startNanos = System.nanoTime(); + return (responseSummary,exception) -> logger.info("path: {} status: {} time: {}", + requestSummary.getRawPath(), + responseSummary.getStatus(), + System.nanoTime() - startNanos); + }); +``` + +By providing more feature rich UniMetric instances you could easily calculate averages per route, uptime, or other fun facts. diff --git a/mkdocs/docs/images/favicon-180.png b/mkdocs/docs/images/favicon-180.png new file mode 100644 index 000000000..a0020180d Binary files /dev/null and b/mkdocs/docs/images/favicon-180.png differ diff --git a/mkdocs/docs/images/kong.png b/mkdocs/docs/images/kong.png new file mode 100644 index 000000000..48c08b432 Binary files /dev/null and b/mkdocs/docs/images/kong.png differ diff --git a/mkdocs/docs/index.md b/mkdocs/docs/index.md new file mode 100644 index 000000000..f395c766f --- /dev/null +++ b/mkdocs/docs/index.md @@ -0,0 +1,31 @@ +# Unirest-Java Documentation + +Welcome to Unirest-Java! + +## About +Unirest is intended to be a simple and obvious library for HTTP requests. It provides a fluent interface that makes discovery of commands easy in a modern IDE. Some of the features it supports are: + + +* HTTP 1 and 2 +* WebSockets +* JSON Patch (RFC-6902) +* Default Object mappers for both Jackson and GSON +* Easy request building including + * Path parameters + * Query param building + * Headers including full suport for manipulating cookies + * Multipart requests + * Turning POJOs into string or binary bodies + * Global request interceptors +* Easy response handling including + * Global response interceptors + * Turning bodies into POJOs + * Error handling +* Mocking library for testing + + +## History + +Unirest-Java started off as just one of series of HTTP clients written in different languages which all conform to the same pattern. + +From the original Unirest-Java though Unirest-Java 3 the library was essentially a wrapper around the excellent Apache HTTP diff --git a/mkdocs/docs/installation.md b/mkdocs/docs/installation.md new file mode 100644 index 000000000..aec0c8927 --- /dev/null +++ b/mkdocs/docs/installation.md @@ -0,0 +1,63 @@ +## Unirest 4 +Unirest 4 is build on modern Java standards, and as such requires at least Java 11. + +Unirest 4's dependencies are fully modular, and have been moved to new Maven coordinates to avoid conflicts with the previous versions. +You can use a maven bom to manage the modules: + +## Install With [Maven](https://mvnrepository.com/artifact/com.konghq/unirest-java)[:](https://repo.maven.apache.org/maven2/com/konghq/unirest-java/) +```xml + + + + + com.konghq + unirest-java-bom + 4.5.1 + pom + import + + + + + + + + com.konghq + unirest-java-core + + + + + + com.konghq + unirest-modules-gson + + + + + com.konghq + unirest-modules-jackson + + +``` + +#### 🚹 Attention JSON users 🚹 +Under Unirest 4, core no longer comes with ANY transient dependencies, and because Java itself lacks a JSON parser you MUST declare a JSON implementation if you wish to do object mappings or use Json objects. + +### Upgrading from Previous Versions +See the [Upgrade Guide](https://github.com/Kong/unirest-java/blob/master/UPGRADE_GUIDE.md) + +### ChangeLog +See the [Change Log](https://github.com/Kong/unirest-java/blob/master/CHANGELOG.md) for recent changes. + + +## Unirest 3 +### Maven +```xml + + + com.konghq + unirest-java + 3.14.1 + +``` \ No newline at end of file diff --git a/mkdocs/docs/mocks.md b/mkdocs/docs/mocks.md new file mode 100644 index 000000000..6a94abb01 --- /dev/null +++ b/mkdocs/docs/mocks.md @@ -0,0 +1,207 @@ +# Unirest Mocks + +## About +A series of mocks for use with Unirest for unit testing. Mocked clients will not make any real web requests. This allows you to test the input into unirest and to mock responses from expected requests. + +```mermaid + flowchart TD + A[Your Code] --> B(Unirest Pubilc Interface) + B --> C{Core Implimentation} + C -->|When Mocked| D[Mocked Client] + C -->|Normal Runtime| F[Java HttpClient] +``` + +## Expecting Requests +You can either mock the default static implementation or a per instance implementation. In both cases you need to register the mock with Unirest. + +### Static Mocking +```java +class MyTest { + @Test + void mockStatic(){ + MockClient mock = MockClient.register(); + + mock.expect(HttpMethod.GET, "http://zombo.com") + .thenReturn("You can do anything!"); + + assertEquals( + "You can do anything!", + Unirest.get("http://zombo.com").asString().getBody() + ); + } +} +``` + +### Instant Mocking +```java + @Test + void mockInstant(){ + UnirestInstance unirest = Unirest.spawnInstance(); + MockClient mock = MockClient.register(unirest); + + mock.expect(HttpMethod.GET, "http://zombo.com") + .thenReturn("You can do anything!"); + + assertEquals( + "You can do anything!", + unirest.get("http://zombo.com").asString().getBody() + ); + } +``` + +### Multiple Expects +HTTP requests can have many parts, some of which are automatic or at least uninteresting from the standpoint of testing. This means that setting up an exact expectation to match the request exactly can be tedious. + +You can register as many expects as you like. Which one is used for any particular invocation of Unirest depends on a points system. Each expectation is evaluated and given points for each positive part while any negative part immediately discards the expect. The expectation that has the most points "wins". + +In this example, we have three expectations, one doesn't match at all. and two others match but one does more than the other so the most specific match is used. + +```java + @Test + void multipleExpects(){ + MockClient mock = MockClient.register(); + + mock.expect(HttpMethod.POST, "https://somewhere.bad") + .thenReturn("I'm Bad"); + + mock.expect(HttpMethod.GET, "http://zombo.com") + .thenReturn("You can do anything!"); + + mock.expect(HttpMethod.GET, "http://zombo.com") + .header("foo", "bar") + .thenReturn("You can do anything with headers!"); + + assertEquals( + "You can do anything with headers!", + Unirest.get("http://zombo.com") + .header("foo", "bar") + .asString().getBody() + ); + + assertEquals( + "You can do anything!", + Unirest.get("http://zombo.com") + .asString().getBody() + ); + } +``` + +### Verifying Expects +Sometimes we only want to know that the needful was done. In this case we can validate our mock. The simplest way is to call verifyAll which will validate that all expects were called at least once. + +```java + @Test + void verifyAll(){ + MockClient mock = MockClient.register(); + + mock.expect(HttpMethod.POST, "http://zombo.com") + .thenReturn().withStatus(200); + + Unirest.post("http://zombo.com").asString().getBody(); + + mock.verifyAll(); + } + +``` + +If you want to get more specific we can keep around our expectations and validate them explicitly. We can also inject a number of times we want to validate (including zero) +```java + @Test + void verifyMultiple(){ + MockClient mock = MockClient.register(); + + var zombo = mock.expect(HttpMethod.POST, "http://zombo.com").thenReturn(); + var homestar = mock.expect(HttpMethod.DELETE, "http://homestarrunner.com").thenReturn(); + + Unirest.post("http://zombo.com").asString().getBody(); + + zombo.verify(); + homestar.verify(Times.never()); + } +``` + +### Expected Body Matching +You can match specific body content with some limitations. Complex bodies must implement ```BodyMatcher```. There are two implementations available: ```EqualsBodyMatcher``` which is used for simple equality and ```FieldMatcher``` which is for form params. You can create your own. + +#### Simple Bodies +```java + @Test + void simpleBody() { + MockClient mock = MockClient.register(); + + mock.expect(HttpMethod.POST, "http://zombo.com") + .body("I can do anything? Anything at all?") + .thenReturn() + .withStatus(201); + + assertEquals(201, + Unirest.post("http://zombo.com").body("I can do anything? Anything at all?").asEmpty().getStatus() + ); + } +``` + +#### Form Params +```java + @Test + void formParams() { + MockClient mock = MockClient.register(); + + mock.expect(HttpMethod.POST, "http://zombo.com") + .body(FieldMatcher.of("foo", "bar", + "baz", "qux")) + .thenReturn() + .withStatus(201); + + assertEquals(201, + Unirest.post("http://zombo.com") + .field("foo", "bar") + .field("baz", "qux") + .asEmpty().getStatus() + ); + } +``` + +## Expected Responses +You can set all properties of a response. +```java + @Test + void response() { + MockClient mock = MockClient.register(); + + mock.expect(HttpMethod.GET, "http://zombo.com") + .thenReturn("Care for some tea mum?") + .withHeader("x-zombo-brewing", "active") + .withStatus(418, "I am a teapot"); + + var response = Unirest.get("http://zombo.com").asString(); + + assertEquals(418, response.getStatus()); + assertEquals("I am a teapot", response.getStatusText()); + assertEquals("Care for some tea mum?", response.getBody()); + assertEquals("active", response.getHeaders().getFirst("x-zombo-brewing")); + } +``` + +### Responses with JSON Bodies +The mocking framework will use whatever ObjectMapper is configured with Unirest to marshall Pojos to expected responses. + +```java + + static class Teapot { public String brewstatus = "on"; } + @Test + void pojos() { + MockClient mock = MockClient.register(); + + mock.expect(HttpMethod.GET, "http://zombo.com") + .thenReturn(new Teapot()); + + var response = Unirest.get("http://zombo.com").asString(); + + assertEquals("{\"brewstatus\":\"on\"}", response.getBody()); + } +``` + + + + + diff --git a/mkdocs/docs/proxies.md b/mkdocs/docs/proxies.md new file mode 100644 index 000000000..df16fa58d --- /dev/null +++ b/mkdocs/docs/proxies.md @@ -0,0 +1,60 @@ +# Proxies +Sometimes you need to tunnel through a proxy. Unirest provides several different mechanisms for this. + +## Simple Proxy +You can set a simple single proxy object. This will create a simple ```java.net.ProxySelector``` and ```java.net.Authenticator``` (if passing creds) +```java + // Configure with authentication: + Unirest.config().proxy("proxy.com", 7777, "username", "password1!"); + + // or without + Unirest.config().proxy("proxy.com", 7777); + + // You can also pass a Unirest Proxy object + Unirest.config().proxy(new Proxy("proxy.com", 7777)); +``` + +## Using System Settings For Proxies +Java has some defined system properties for Proxies. +```java + System.setProperty("http.proxyHost", "localhost"); + System.setProperty("http.proxyPort", "7777"); + + Unirest.config().useSystemProperties(true); +``` + +## Dealing with Multiple Proxies +Sometimes, if the universe hates you. You might need to use different proxies for different hosts. +This can be done by using a ```ProxySelector``` (and a ```Authenticator``` if auth is required) + +```java + Unirest.config() + .proxy(new ProxySelector() { + @Override + public List select(URI uri) { + if (uri.getHost().equals("homestarrunner.com")) { + return List.of(new java.net.Proxy(HTTP, InetSocketAddress.createUnresolved("proxy-sad.com", 7777))); + } + + return List.of(new java.net.Proxy(HTTP, InetSocketAddress.createUnresolved("default.com", 7777))); + } + + @Override + public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { + + } + }) + .authenticator(new Authenticator() { + @Override + public PasswordAuthentication requestPasswordAuthenticationInstance(String host, InetAddress addr, + int port, String protocol, + String prompt, String scheme, + URL url, RequestorType reqType) { + // Please don't hardcode passwords in your code :D + if(host.equals("homestarrunner.com")) { + return new PasswordAuthentication("strongbad", "password".toCharArray()); + } + return new PasswordAuthentication("default", "password".toCharArray()); + } + }); +``` \ No newline at end of file diff --git a/mkdocs/docs/requests.md b/mkdocs/docs/requests.md new file mode 100644 index 000000000..2b599e627 --- /dev/null +++ b/mkdocs/docs/requests.md @@ -0,0 +1,221 @@ +# Requests +So you're probably wondering how using Unirest makes creating requests in Java easier, here is a basic POST request that will explain everything: + +```java +HttpResponse response = Unirest.post("http://localhost/post") + .header("accept", "application/json") + .queryString("apiKey", "123") + .field("parameter", "value") + .field("firstname", "Gary") + .asJson(); +``` + +Requests are made when `as[Type]()` is invoked, possible types include `Json`, `String`, `Object` `Empty` and `File`. + + +## Route Parameters +Sometimes you want to add dynamic parameters in the URL, you can easily do that by adding a placeholder in the URL, and then by setting the route parameters with the `routeParam` function, like: + +```java +Unirest.get("http://localhost/{fruit}") + .routeParam("fruit", "apple") + .asString(); + +// Results in `http://localhost/apple` +``` +The placeholder `{fruit}` will be replaced with `apple`. + +The placeholder's format is as easy as wrapping in curly braces: `{custom_name}` + +All param values will be URL-Encoded for you + +## Default Base URLs +You can configure a default base URL to be used for all requests that do not contain a full URL. + +This configuration will result in a GET to "http://homestar.com/runner" +```java + Unirest.config().defaultBaseUrl("http://homestar.com"); + + Unirest.get("/runner").asString(); +``` + +## Query Parameters +Query-string params can be built up one by one + +```java +Unirest.get("http://localhost") + .queryString("fruit", "apple") + .queryString("droid", "R2D2") + .asString(); + +// Results in "http://localhost?fruit=apple&droid=R2D2" +``` + +Again all param values will be URL-Encoded. + +You can also pass in query strings as arrays and maps: +```java +Unirest.get("http://localhost") + .queryString("fruit", Arrays.asList("apple", "orange")) + .queryString(ImmutableMap.of("droid", "R2D2", "beatle", "Ringo")) + .asString(); + + // Results in "http://localhost?fruit=apple&fruit=orange&droid=R2D2&beatle=Ringo" +``` + +## Headers +Request headers can be added with the ```header``` method. +```java +Unirest.get("http://localhost") + .header("Accept", "application/json") + .header("x-custom-header", "hello") + .asString(); +``` + +### Basic Authentication +Unirest exposes a shortcut for doing basic auth when you need to. Unirest handles the Base64 encoding part. +Please make sure you are always doing this over HTTPS! + +```java +Unirest.get("http://localhost") + .basicAuth("user", "password1!") + .asString(); + +// this adds the header "Authorization: Basic dXNlcjpwYXNzd29yZDEh" +``` + +## Body Data + +### Entity Bodies +You can post entity objects as the full body easily. This is the default behavior of most REST services. + +Unless you specify otherwise the default ```Content-Type``` is ```text/plain; charset=UTF-8``` + +```java +Unirest.post("http://localhost") + .body("This is the entire body") + .asEmpty(); +``` + +You can also post as a Object that is serialized using a configured ObjectMapper. (see [Object Mappers](#object-mappers) for implementation details. +Unirest comes with a default mapper that will serialize to json using the popular Google Gson library +```java +Unirest.post("http://localhost") + .header("Content-Type", "application/json") + .body(new SomeUserObject("Bob")) + .asEmpty(); + +// This will use Jackson to serialize the object into JSON. +``` + +### JSON Patch Bodies +Unirest has full native support for JSON Patch requests (RFC-6902 see ) +Per the spec, the default ```Content-Type``` for json-patch is ```application/json-patch+json``` + +```java + Unirest.jsonPatch("http://localhost") + .add("/fruits/-", "Apple") + .remove("/bugs") + .replace("/lastname", "Flintstone") + .test("/firstname", "Fred") + .move("/old/location", "/new/location") + .copy("/original/location", "/new/location") + .asJson(); +``` +will send a request with a body of +```js + [ + {"op":"add","path":"/fruits/-","value":"Apple"}, + {"op":"remove","path":"/bugs"}, + {"op":"replace","path":"/lastname","value":"Flintstone"}, + {"op":"test","path":"/firstname","value":"Fred"}, + {"op":"move","path":"/new/location","from":"/old/location"}, + {"op":"copy","path":"/new/location","from":"/original/location"} + ] +``` + +### Basic Forms +Basic http name value body params can be passed with simple field calls. +The ```Content-Type``` for this type of request is defaulted to ```application/x-www-form-urlencoded``` + +```java +Unirest.post("http://localhost") + .field("fruit", "apple") + .field("droid", "R2D2") + .asEmpty(); + + // This will post a simple name-value pair body the same as a HTML form. This looks like + // `fruit=apple&droid=R2D2' +``` + +### File Uploads +You can also post binary data in a form. Like a file. + +The ```Content-Type``` for this type of request is defaulted to ```multipart/form-data``` + +```java +Unirest.post("http://localhost") + .field("upload", new File("/MyFile.zip")) + .asEmpty(); +``` + +For large files you may want to use a InputStream. Pass it a file name if you want one. +We are using a FileInputStream here but it can actually be any kind of InputStream. + +```java +InputStream file = new FileInputStream(new File("/MyFile.zip")); + +Unirest.post("http://localhost") + .field("upload", file, "MyFile.zip") + .asEmpty(); +``` + +### Upload Progress Monitoring +If you are uploading large files you might want to provide some time of progress bar to a user. You can monitor this progress by providing a ProgresMonitor. + +```java + Unirest.post("http://localhost") + .field("upload", new File("/MyFile.zip")) + .uploadMonitor((field, fileName, bytesWritten, totalBytes) -> { + updateProgressBarWithBytesLeft(totalBytes - bytesWritten); + }) + .asEmpty(); +``` + +## Asynchronous Requests +Sometimes, well most of the time, you want your application to be asynchronous and not block, Unirest supports this in Java using anonymous callbacks, or direct method placement. All request types also support async versions. + +```java +CompletableFuture> future = Unirest.post("http://localhost/post") + .header("accept", "application/json") + .field("param1", "value1") + .field("param2", "value2") + .asJsonAsync(response -> { + int code = response.getStatus(); + JsonNode body = response.getBody(); + }); +``` + +## Paged Requests +Sometimes services offer paged requests. How this is done is not standardized but Unirest proves a mechanism to follow pages until all have been consumed. You must provide two functions for extracting the next page. The first is to get the HttpResponse in the format you want, the other is to extract the ```next``` link from the response. The result is a ```PagedList``` of ```HttpResponse```. The paged list has some handy methods for dealing with the results. Here we are getting a paged list of Dogs where the ```next``` link is in the headers. + +```java +PagedList result = Unirest.get("https://somewhere/dogs") + .asPaged( + r -> r.asObject(Doggos.class), + r -> r.getHeaders().getFirst("nextPage") + ); + +``` + +## Client Certificates +In case you need to use a custom client certificate to call a service you can provide unirest with a custom keystore. +You may either pass a KeyStore object or a path to a valid PKCS#12 keystore file. + +```java +Unirest.config() + .clientCertificateStore("/path/mykeystore.p12", "password1!"); + +Unirest.get("https://some.custom.secured.place.com") + .asString(); +``` \ No newline at end of file diff --git a/mkdocs/docs/responses.md b/mkdocs/docs/responses.md new file mode 100644 index 000000000..85d24e910 --- /dev/null +++ b/mkdocs/docs/responses.md @@ -0,0 +1,164 @@ +# Responses +Unirest makes the actual request the moment you invoke one of its ```as[type]``` methods. These methods also inform Unirest what type to map the response to. Options are ```Empty```, ```String```, ```File```, ```Object```, ```byte``` and ```Json```. + +The response returns as a ```HttpResponse``` where the ```HttpResponse``` object has all of the common response data like status and headers. The Body (if present) can be accessed via the desired type with the ```.getBody()``` method. + +## Empty Responses +If you aren't expecting a body back, or you don't care about the body, ```asEmpty``` is the easiest choice. You will still get back response information like status and headers. Note that this method effectively ignores any body that might be present. + +```java +HttpResponse response = Unirest.delete("http://localhost").asEmpty(); +``` + +## String Responses +The next easiest response type is String. You can do whatever you want with it after that. + +```java +String body = Unirest.get("http://localhost") + .asString() + .getBody(); +``` + +## Object Mapped Responses +Most of the time when consuming RESTful services you probably want to map the response into an object. + +For this you need to provide the Unirest configuration with an implementation of ```ObjectMapper``` (see [Object Mappers](#object-mappers) for details.). + +If the response is JSON you are in luck and Unirest comes with a basic ```JsonObjectMapper``` based on Google GSON. + +Before an `asObject(Class)` it is necessary to provide a custom implementation of the `ObjectMapper` interface (if you do not wish to use the default mapper). This should be done only the first time, as the instance of the ObjectMapper will be shared globally. + +Unirest offers a few plug-ins implementing popular object mappers like Jackson and Gson. See [mvn central](https://mvnrepository.com/artifact/com.konghq) for details. + +For example, +```java +// Response to Object +Book book = Unirest.get("http://localhost/books/1") + .asObject(Book.class) + .getBody(); + +// Generic types can be resolved by using a GenericType subclass to avoid erasure +List books = Unirest.get("http://localhost/books/") + .asObject(new GenericType>(){}) + .getBody(); + +Author author = Unirest.get("http://localhost/books/{id}/author") + .routeParam("id", bookObject.getId()) + .asObject(Author.class) + .getBody(); +``` + +### Errors in Object or JSON parsing +You can't always get what you want. And sometimes results you get from web services will not map into what you expect them to. +When this happens with a ```asObject``` or ```asJson``` request the resulting body will be null, but the response object will contain a ParsingException that allows you to get the error and the original body for inspection. + +```java +UnirestParsingException ex = response.getParsingError().get(); + +ex.getOriginalBody(); // Has the original body as a string. +ex.getMessage(); // Will have the parsing exception. +ex.getCause(); // of course will have the original parsing exception itself. +``` + +### Mapping Error Objects +Sometimes with REST API's the service will return a error object that can be parsed. You can optionally map this into an POJO like + +```java + HttpResponse book = Unirest.get("http://localhost/books/{id}") + .asObject(Book.class); + + // This will be null if there wasn't an error + Error er = book.mapError(Error.class); + + // You can also take advantage of this inside of the ifFailure method + Unirest.get("http://localhost/books/{id}") + .asObject(Book.class) + .ifFailure(Error.class, r -> { + Error e = r.getBody(); + }); +``` + +### Mapping one body type to another without an object mapper +If you don't want to provide a full ObjectMapper implementation you may use a simple function to map the response + +```java + int body = Unirest.get("http://httpbin/count") + .asString() + .mapBody(Integer::valueOf); +``` + +## File Responses +Sometimes you just want to download a file, or maybe capture the response body into a file. Unirest can do both. Just tell Unirest where you want to put the file. + +```java +File result = Unirest.get("http://some.file.location/file.zip") + .asFile("/disk/location/file.zip") + .getBody(); +``` + +### Download Progress Monitoring +If you are uploading large files you might want to provide some time of progress bar to a user. You can monitor this progress by providing a ProgresMonitor. + +```java + Unirest.get("http://localhost") + .downLoadMonitor((b, fileName, bytesWritten, totalBytes) -> { + updateProgressBarWithBytesLeft(totalBytes - bytesWritten); + }) + .asFile("/disk/location/file.zip"); +``` + +## JSON responses +Unirest offers a lightweight JSON response type when you don't need a full Object Mapper. + +```java +String result = Unirest.get("http://some.json.com") + .asJson() + .getBody() + .getObject() + .getJSONObject("car") + .getJSONArray("wheels") + .get(0) +``` + + +## Large Responses +Some response methods (```asString```, ```asJson```) read the entire +response stream into memory. In order to read the original stream and handle large responses you +can use several functional methods like: + +```java + Map r = Unirest.get(MockServer.GET) + .queryString("firstname", "Gary") + .asObject(i -> new Gson().fromJson(i.getContentReader(), HashMap.class)) + .getBody(); + +``` + +or consumers: + +```java + + Unirest.get(MockServer.GET) + .thenConsumeAsync(r -> { + // something like writing a file to disk + }); +``` + +## Error Handling +the HttpResponse object has a few handler methods that can be chained to deal with success and failure: + * ```ifSuccess(Consumer> response)``` will be called if the response was a 200-series response and any body processing (like ```json``` or ```Object``` was successful. + * ```ifFailure(Consumer response``` will be called if the status was 400+ or body processing failed. + +Putting them together might look like this: +```java + Unirest.get("http://somewhere") + .asJson() + .ifSuccess(response -> someSuccessMethod(response)) + .ifFailure(response -> { + log.error("Oh No! Status" + response.getStatus()); + response.getParsingError().ifPresent(e -> { + log.error("Parsing Exception: ", e); + log.error("Original body: " + e.getOriginalBody()); + }); + }); +``` diff --git a/mkdocs/docs/server-sent-events.md b/mkdocs/docs/server-sent-events.md new file mode 100644 index 000000000..2cb1406bb --- /dev/null +++ b/mkdocs/docs/server-sent-events.md @@ -0,0 +1,32 @@ + +## About Server Sent Events +Server-Sent Events (SSE) is a server push technology enabling a client to receive automatic updates from a server via an HTTP connection, and describes how servers can initiate data transmission towards clients once an initial client connection has been established. They are commonly used to send message updates or continuous data streams to a browser client and designed to enhance native, cross-browser streaming through a JavaScript API called EventSource, through which a client requests a particular URL in order to receive an event stream. The EventSource API is standardized as part of [HTML Living Standard by the WHATWG](https://html.spec.whatwg.org/multipage/server-sent-events.html). +The default media type for SSE is ```text/event-stream```. + +## Consuming Server Sent Events With Unirest-Java +Unirest has two ways to consume a SSE web service; one async and one synchronous. Please be mindful that SSE is a persistent connection and unirest will keep the connection open as long as the server is willing and able. For this reason you may find the async method a better fit for most production systems. + +NOTE: In order for object mapping to occur you must configure an ObjectMapper with unirest. + +### Async Call +The following subscribes to wikipedia's SSE stream of recently changed pages. Maps each event into a POJO, and outputs the name of the changed page. +Note that Unirest will return you a ```CompletableFuture``` which you can hold on to to monitor the process. +```java + var future = Unirest.sse("https://stream.wikimedia.org/v2/stream/recentchange") + .connect(event -> { + var change = event.asObject(RecentChange.class); + System.out.println("Changed Page: " + change.getTitle()); + }); + +``` + + +### Synchronous Call +The following subscribes to wikipedia's SSE stream of recently changed pages. Maps each event into a POJO, and outputs the name of the changed page. +```java + Unirest.sse("https://stream.wikimedia.org/v2/stream/recentchange") + .connect() + .map(event -> event.asObject(RecentChange.class)) + .forEach(change -> System.out.println("Changed Page: " + change.getTitle())); + +``` diff --git a/mkdocs/docs/upgrade-guide.md b/mkdocs/docs/upgrade-guide.md new file mode 100644 index 000000000..3251412db --- /dev/null +++ b/mkdocs/docs/upgrade-guide.md @@ -0,0 +1,178 @@ +# Upgrade Guide + +## Upgrading to Unirest 4.3 +The modules have been repackaged and put into new maven coordinates in order to avoid conflicts with the 3.x line of unirest. +The mock module has had its maven artifact ID changed only. + +| Old Maven Artifact ID | New Maven Artifact ID | Old Class Package | New Class Package | +|----|-------------------------|----------------------|------------------------------| +|unirest-object-mappers-gson | unirest-modules-gson | kong.unirest.gson | kong.unirest.modules.gson | +|unirest-objectmapper-jackson | unirest-modules-jackson | kong.unirest.jackson | kong.unirest.modules.jackson | +|unirest-mocks | unirest-modules-mocks | kong.unirest.core | kong.unirest.core | + + +## Upgrading to Unirest 4.0 + 🚹 **Unirest 4 drops the Apache Http Client dependency in favor of the pure Java client** 🚹. This means that unirest 4 has a dependency on Java-11. Unirest 3 will continue to be supported for bugs and minor features. If you still haven't upgraded from Java 8 now is the time! + +Due to engine changes there are some differences in behavior. Efforts have been made to limit these changes. The others are documented here. + +### How to upgrade +* The core Unirest package has been moved from ```kong.unirest``` to ```kong.unirest.core```, this prevents classloader issues where Unirest 4 may conflict with previous versions which may also be on the classpath. +* Unirest no longer comes with GSON as a default JSON parser. In order to enable JSON support you must include either the GSON or Jackson JSON packages with: +```xml + + com.konghq + unirest-object-mappers-gson + 4.2.4 + + + + com.konghq + unirest-objectmapper-jackson + 4.2.4 + +``` +* In order to assist with the various modules Unirest now includes a dependency management BOM. Include the BOM in your dependency management section and then just declare the modules you want without the version. + +```xml + + + + com.konghq + unirest-java-bom + 4.2.4 + pom + import + + + + + + + com.konghq + unirest-core + + + com.konghq + unirest-object-mappers-gson + + +``` + +### Differences +* null header values are now represented by an empty string rather than null. +* response headers may return a different order that with Apache. +* You may not normally override the `host` header anymore. Starting with Java-12 you may only due this by setting a system property of jdk.httpclient.allowRestrictedHeaders=host. see https://bugs.openjdk.java.net/browse/JDK-8213696 +* Cookie management follows more modern standards and may differ from apache with regard to non-standard parsing. + * related: ```config.cookieSpec(String)``` has been removed as it was Apache specific. +* Per-Request proxies are no longer supported. +* Custom HostNameVerifier is no longer supported. +* Socket timeout is no longer set independent of connection timeout and has been removed +* There are no longer any monitoring threads to shut down, as such, all close methods and the registering of shutdown hooks have been removed. +* Using system props for proxy settings is false by default (was true in previous versions) +* max concurrent routes is no longer supported as this was a feature of Apache. concurrency(int, int) has been removed. + +## Upgrading to Unirest 3.0 +The primary difference in Unirest 3 is that the org.json dependency has been replaced by a clean-room implementation of org.json's interface using Google Gson as the engine. + +### What? Why? +This was done due to conflicts with the org.json license which requires that "The Software shall be used for Good, not Evil.". While many people would rightly view this as silly and unenforceable by law, many organizations such as Eclipse, Debian, and Apache will not allow using it. + +### Why not switch to the google implementation of org.json? +Several reasons: +* It has not been maintained in several years and no longer matches the org.json signatures. +* It causes classpath conflicts which many projects forbid. +* We would like Unirest to be able to expand beyond org.json and offer more advanced native features like object mapping. + +### Why Gson and not Jackson? +* Gson is closest in spirit and method signature to org.json and was deemed quicker to adopt. +* It's small, mature and a single dependency. +* It would conflict less in other projects than Jackson would which is both more popular and far more complex. + +### How was this done? +Implementation was done without looking at the internals of the org.json classes. This was accomplished by writing extensive unit tests in order to document behavior and method signatures and then simply changing the test to use this projects own classes as well as Google Gson. + +### Differences between org.json and kong.unirest.core.json +* The namespace is now ```kong.unirest.core.json``` +* For the most part kong.unirest.core.json honors all public interfaces and behavior of ```JSONArray```, ```JSONObject```, and ```JSONPointer```. +* The utility classes in org.json have NOT been implemented as they are not required for Unirest's use case. So things like XML-to-JSON, and CSV-to-JSON have not been implemented. +* Custom indenting with ```.toString(int spaces)``` does not honor the indent factor and always uses 2 spaces. Waiting on https://github.com/google/gson/pull/1280 for a fix. +* There are some slight differences in the details of some error messages. + + +## Upgrading to Unirest 2.0 from previous versions + +### Package +All main classes are now in the ```kong.unirest``` package. Classes related to the underlying Apache Http client that powers unirest are kept in ```kong.unirest.apache``` This project doesn't have many files, it really doesn't need anything more complicated than that. + +### Removed Methods and Java Requirements +* Java 8: Java 8 is now required for Unirest due to extensive lambda support. +* ```.asBinary()``` and ```.getRawResponse()``` methods have been removed. These have been replaced by consumer methods which allow you to read the InputStream directly and not a copy. (see ```HttpRequest::thenConsume(Consumer consumer)``` +* Removal of all Apache classes in the non-config interfaces. These have ben replaced by Unirest native interfaces. + Typically these interfaces are very similar to the older Apache classes and so updating shouldn't be a problem. + + + +### Configuration +Previous versions of unirest had configuration split across several different places. + +```java + // Sometimes it was on Unirest + Unirest.setTimeouts(5000, 10000); + + //Sometimes it was on Options + Options.setOption(HTTPCLIENT, client); +``` + +Often you could do it in both places with different impacts on the lifecycle of the client. All configuration has now been centralized in ```Unirest.config()``` + +#### Unirest.config() +Unirest config allows easy access to build a configuration just like you would build a request: + +```java + Unirest.config() + .connectTimeout(1000) + .proxy(new Proxy("https://proxy")) + .setDefaultHeader("Accept", "application/json") + .followRedirects(false) + .enableCookieManagement(false) + .addInterceptor(new MyCustomInterceptor()); +``` + +##### Changing the config +Changing Unirest's config should ideally be done once, or rarely. There are several background threads spawned by both Unirest itself and Apache HttpAsyncClient. Once Unirest has been activated configuration options that are involved in creating the client cannot be changed without an explicit shutdown or reset. + +```Java + Unirest.config() + .reset() + .connectTimeout(5000) +``` + +##### Setting custom Apache Client +You can set your own custom Apache HttpClient and HttpAsyncClient. Note that Unirest settings like timeouts or interceptors are not applied to custom clients. + +```java + Unirest.config() + .httpClient(myClient) + .asyncClient(myAsyncClient) +``` + +#### Multiple Configuration Instances +As usual, Unirest maintains a primary single instance. Sometimes you might want different configurations for different systems. You might also want an instance rather than a static context for testing purposes. + +```java + + // this returns the same instance used by Unirest.get("http://somewhere/") + UnirestInstance unirest = Unirest.primaryInstance(); + // It can be configured and used just like the static context + unirest.config().connectTimeout(5000); + String result = unirest.get("http://foo").asString().getBody(); + + // You can also get a whole new instance + UnirestInstance unirest = Unirest.spawnInstance(); +``` + +**WARNING!** If you get a new instance of unirest YOU are responsible for shutting it down when the JVM shuts down. It is not tracked or shut down by ```Unirest.shutDown();``` + + + diff --git a/mkdocs/mkdocs.yml b/mkdocs/mkdocs.yml new file mode 100644 index 000000000..dbd7b8adc --- /dev/null +++ b/mkdocs/mkdocs.yml @@ -0,0 +1,57 @@ +site_name: Unirest-Java Documentation +site_url: https://kong.github.io/unirest-java/installation +repo_url: https://github.com/Kong/unirest-java +repo_name: Kong/unirest-java +theme: + name: material + favicon: images/favicon-180.png + logo: images/kong.png + palette: + # Palette toggle for automatic mode + - media: "(prefers-color-scheme)" + toggle: + icon: material/brightness-auto + name: Switch to light mode + + # Palette toggle for light mode + - media: "(prefers-color-scheme: light)" + scheme: default + toggle: + icon: material/brightness-7 + name: Switch to dark mode + + # Palette toggle for dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + toggle: + icon: material/brightness-4 + name: Switch to system preference + features: + - content.code.copy + - content.code.select + +nav: + - About: index.md + - Installation: installation.md + - Upgrade Guide: upgrade-guide.md + - Configuration: configuration.md + - Making Requests: requests.md + - Handling Responses: responses.md + - Server Sent Events: server-sent-events.md + - Caching Responses: caching.md + - Mocking: mocks.md + - Proxies: proxies.md + + +markdown_extensions: + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format \ No newline at end of file diff --git a/mkdocs/requirements.txt b/mkdocs/requirements.txt new file mode 100644 index 000000000..898468cb1 --- /dev/null +++ b/mkdocs/requirements.txt @@ -0,0 +1 @@ +mkdocs-material \ No newline at end of file diff --git a/pom.xml b/pom.xml index 764152684..4a63628c7 100644 --- a/pom.xml +++ b/pom.xml @@ -1,143 +1,390 @@ - + - 4.0.0 - com.mashape.unirest - unirest-java - jar - 1.4.10-SNAPSHOT - unirest-java - Simplified, lightweight HTTP client library - http://unirest.io/ + 4.0.0 + com.konghq + unirest-java-parent + pom + 4.10.3-SNAPSHOT + unirest + Parent pom for unirest packages + http://github.com/Kong/unirest-java/ - - org.sonatype.oss - oss-parent - 7 - + + + MIT + http://opensource.org/licenses/MIT + repo + + - - - MIT - http://opensource.org/licenses/MIT - repo - - + + unirest + unirest-modules-gson + unirest-modules-jackson + unirest-modules-jackson-legacy + unirest-modules-mocks + unirest-bdd-tests + unirest-java-bom + + - - https://github.com/Mashape/unirest-java - scm:git:git@github.com:Mashape/unirest-java.git - scm:git:git@github.com:Mashape/unirest-java.git - + + UTF-8 + UTF-8 + 3.1.1 + ${project.basedir} + 6.0.3 + 5.20.0 + 3.6.0 + 3.27.7 + 3.5.4 + - - - mashape - Mashape - opensource@mashape.com - https://github.com/Mashape - Mashape - https://www.mashape.com - - + + https://github.com/Kong/unirest-java + scm:git:git@github.com:Kong/unirest-java.git + scm:git:git@github.com:Kong/unirest-java.git + HEAD + - - UTF-8 - UTF-8 - 2.6.0 - + + + Ryber + Ryan Bergman + https://github.com/ryber + + Maintainer + + + - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.1 - - true - 128m - 512m - 1.5 - 1.5 - UTF-8 - - - - org.apache.maven.plugins - maven-assembly-plugin - 2.4 - - - jar-with-dependencies - - - - - org.apache.maven.plugins - maven-source-plugin - 2.2.1 - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.9.1 - - - attach-javadocs - - jar - - - - - - + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + + ossrh + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + sign-artifacts + verify + + sign + + + + + + + + + + + + + com.mycila.maven-license-plugin + maven-license-plugin + 1.8.0 + + +
${main.dir}/build/fileHeader.txt
+ true + + **/*.scss + **/*.b64 + **/*.svg + **/Gemfile + .java-version + .java-version + **/*.js + **/*.json + **/*.p12 + **/*.pem + **/*.html + **/*.xml + **/*.txt + **/*.log + **/*.md + **/*.yml + .idea/** + .github/** + **/*.scssc + **/.jekyll-metadata + .git* + LICENSE + Gemfile.lock + +
+ + + validate + + format + + + +
+ + org.apache.maven.plugins + maven-jar-plugin + 3.1.0 + + + + Unirest + + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${maven-checkstyle-plugin.version} + + + com.puppycrawl.tools + checkstyle + 10.26.1 + + + + + compile + auto-checkstyle + + checkstyle + check + + + + + build/checkstyle.xml + build/suppressions.xml + true + true + false + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + -Duser.timezone=UTC + + + + org.apache.maven.plugins + maven-failsafe-plugin + ${maven-surefire-plugin.version} + + + org.jacoco + jacoco-maven-plugin + 0.8.15 + + + default-prepare-agent + + prepare-agent + + + + default-report + + report + + + + default-check + + check + + + + + BUNDLE + + + COMPLEXITY + COVEREDRATIO + 0.60 + + + + + + + + + + org.apache.maven.plugins + maven-release-plugin + 3.0.0 + + v@{project.version} + + + + org.sonatype.central + central-publishing-maven-plugin + 0.8.0 + true + + central + true + + + + + + + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + true + 128m + 512m + 11 + 11 + UTF-8 + + + + org.apache.maven.plugins + maven-source-plugin + 3.3.1 + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.12.0 + + 11 + all,-missing + + + + attach-javadocs + + jar + + + + +
+
+ + + + + + org.mockito + mockito-core + ${mockito.version} + test + + + org.mockito + mockito-junit-jupiter + ${mockito.version} + test + + + + org.assertj + assertj-core + ${assertj.version} + test + + + + org.assertj + assertj-guava + ${assertj.version} + test + + + org.junit.jupiter + junit-jupiter-api + ${junit.version} + test + + + org.junit.jupiter + junit-jupiter-engine + ${junit.version} + test + + + org.junit.jupiter + junit-jupiter-params + ${junit.version} + test + + + org.skyscreamer + jsonassert + 1.5.3 + test + + + + com.google.guava + guava + 33.5.0-jre + test + + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${maven-checkstyle-plugin.version} + + + + checkstyle + + + + + + - - - org.apache.httpcomponents - httpclient - 4.5.2 - - - org.apache.httpcomponents - httpasyncclient - 4.1.1 - - - org.apache.httpcomponents - httpmime - 4.5.2 - - - org.json - json - 20160212 - - - junit - junit - 4.12 - test - - - commons-io - commons-io - 2.4 - test - - - com.fasterxml.jackson.core - jackson-databind - ${jackson.version} - test - -
diff --git a/reports.sh b/reports.sh new file mode 100755 index 000000000..f26737a89 --- /dev/null +++ b/reports.sh @@ -0,0 +1,26 @@ +# +# The MIT License +# +# Copyright for portions of unirest-java are held by Kong Inc (c) 2013. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +open ./unirest/target/site/jacoco/index.html \ No newline at end of file diff --git a/src/main/java/com/mashape/unirest/http/Headers.java b/src/main/java/com/mashape/unirest/http/Headers.java deleted file mode 100644 index 662485020..000000000 --- a/src/main/java/com/mashape/unirest/http/Headers.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.mashape.unirest.http; - -import java.util.HashMap; -import java.util.List; - -public class Headers extends HashMap> { - - private static final long serialVersionUID = 71310341388734766L; - - public String getFirst(Object key) { - List list = get(key); - if (list != null && list.size() > 0) { - return list.get(0); - } - return null; - } - -} diff --git a/src/main/java/com/mashape/unirest/http/HttpClientHelper.java b/src/main/java/com/mashape/unirest/http/HttpClientHelper.java deleted file mode 100644 index bc84b9ab7..000000000 --- a/src/main/java/com/mashape/unirest/http/HttpClientHelper.java +++ /dev/null @@ -1,245 +0,0 @@ -/* -The MIT License - -Copyright (c) 2013 Mashape (http://mashape.com) - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.mashape.unirest.http; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.net.URI; -import java.net.URL; -import java.net.URLDecoder; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import org.apache.http.HttpEntity; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpHead; -import org.apache.http.client.methods.HttpOptions; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpPut; -import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.concurrent.FutureCallback; -import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; -import org.apache.http.nio.entity.NByteArrayEntity; - -import com.mashape.unirest.http.async.Callback; -import com.mashape.unirest.http.async.utils.AsyncIdleConnectionMonitorThread; -import com.mashape.unirest.http.exceptions.UnirestException; -import com.mashape.unirest.http.options.Option; -import com.mashape.unirest.http.options.Options; -import com.mashape.unirest.http.utils.ClientFactory; -import com.mashape.unirest.request.HttpRequest; - -public class HttpClientHelper { - - private static final String CONTENT_TYPE = "content-type"; - private static final String ACCEPT_ENCODING_HEADER = "accept-encoding"; - private static final String USER_AGENT_HEADER = "user-agent"; - private static final String USER_AGENT = "unirest-java/1.3.11"; - - private static FutureCallback prepareCallback(final Class responseClass, final Callback callback) { - if (callback == null) - return null; - - return new FutureCallback() { - - public void cancelled() { - callback.cancelled(); - } - - public void completed(org.apache.http.HttpResponse arg0) { - callback.completed(new HttpResponse(arg0, responseClass)); - } - - public void failed(Exception arg0) { - callback.failed(new UnirestException(arg0)); - } - - }; - } - - public static Future> requestAsync(HttpRequest request, final Class responseClass, Callback callback) { - HttpUriRequest requestObj = prepareRequest(request, true); - - CloseableHttpAsyncClient asyncHttpClient = ClientFactory.getAsyncHttpClient(); - if (!asyncHttpClient.isRunning()) { - asyncHttpClient.start(); - AsyncIdleConnectionMonitorThread asyncIdleConnectionMonitorThread = (AsyncIdleConnectionMonitorThread) Options.getOption(Option.ASYNC_MONITOR); - asyncIdleConnectionMonitorThread.start(); - } - - final Future future = asyncHttpClient.execute(requestObj, prepareCallback(responseClass, callback)); - - return new Future>() { - - public boolean cancel(boolean mayInterruptIfRunning) { - return future.cancel(mayInterruptIfRunning); - } - - public boolean isCancelled() { - return future.isCancelled(); - } - - public boolean isDone() { - return future.isDone(); - } - - public HttpResponse get() throws InterruptedException, ExecutionException { - org.apache.http.HttpResponse httpResponse = future.get(); - return new HttpResponse(httpResponse, responseClass); - } - - public HttpResponse get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { - org.apache.http.HttpResponse httpResponse = future.get(timeout, unit); - return new HttpResponse(httpResponse, responseClass); - } - }; - } - - public static HttpResponse request(HttpRequest request, Class responseClass) throws UnirestException { - HttpRequestBase requestObj = prepareRequest(request, false); - HttpClient client = ClientFactory.getHttpClient(); // The - // DefaultHttpClient - // is thread-safe - - org.apache.http.HttpResponse response; - try { - response = client.execute(requestObj); - HttpResponse httpResponse = new HttpResponse(response, responseClass); - requestObj.releaseConnection(); - return httpResponse; - } catch (Exception e) { - throw new UnirestException(e); - } finally { - requestObj.releaseConnection(); - } - } - - private static HttpRequestBase prepareRequest(HttpRequest request, boolean async) { - - Object defaultHeaders = Options.getOption(Option.DEFAULT_HEADERS); - if (defaultHeaders != null) { - @SuppressWarnings("unchecked") - Set> entrySet = ((Map) defaultHeaders).entrySet(); - for (Entry entry : entrySet) { - request.header(entry.getKey(), entry.getValue()); - } - } - - if (!request.getHeaders().containsKey(USER_AGENT_HEADER)) { - request.header(USER_AGENT_HEADER, USER_AGENT); - } - if (!request.getHeaders().containsKey(ACCEPT_ENCODING_HEADER)) { - request.header(ACCEPT_ENCODING_HEADER, "gzip"); - } - - HttpRequestBase reqObj = null; - - String urlToRequest = null; - try { - URL url = new URL(request.getUrl()); - URI uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), URLDecoder.decode(url.getPath(), "UTF-8"), "", url.getRef()); - urlToRequest = uri.toURL().toString(); - if (url.getQuery() != null && !url.getQuery().trim().equals("")) { - if (!urlToRequest.substring(urlToRequest.length() - 1).equals("?")) { - urlToRequest += "?"; - } - urlToRequest += url.getQuery(); - } else if (urlToRequest.substring(urlToRequest.length() - 1).equals("?")) { - urlToRequest = urlToRequest.substring(0, urlToRequest.length() - 1); - } - } catch (Exception e) { - throw new RuntimeException(e); - } - - switch (request.getHttpMethod()) { - case GET: - reqObj = new HttpGet(urlToRequest); - break; - case POST: - reqObj = new HttpPost(urlToRequest); - break; - case PUT: - reqObj = new HttpPut(urlToRequest); - break; - case DELETE: - reqObj = new HttpDeleteWithBody(urlToRequest); - break; - case PATCH: - reqObj = new HttpPatchWithBody(urlToRequest); - break; - case OPTIONS: - reqObj = new HttpOptions(urlToRequest); - break; - case HEAD: - reqObj = new HttpHead(urlToRequest); - break; - } - - Set>> entrySet = request.getHeaders().entrySet(); - for (Entry> entry : entrySet) { - List values = entry.getValue(); - if (values != null) { - for (String value : values) { - reqObj.addHeader(entry.getKey(), value); - } - } - } - - // Set body - if (!(request.getHttpMethod() == HttpMethod.GET || request.getHttpMethod() == HttpMethod.HEAD)) { - if (request.getBody() != null) { - HttpEntity entity = request.getBody().getEntity(); - if (async) { - if (reqObj.getHeaders(CONTENT_TYPE) == null || reqObj.getHeaders(CONTENT_TYPE).length == 0) { - reqObj.setHeader(entity.getContentType()); - } - try { - ByteArrayOutputStream output = new ByteArrayOutputStream(); - entity.writeTo(output); - NByteArrayEntity en = new NByteArrayEntity(output.toByteArray()); - ((HttpEntityEnclosingRequestBase) reqObj).setEntity(en); - } catch (IOException e) { - throw new RuntimeException(e); - } - } else { - ((HttpEntityEnclosingRequestBase) reqObj).setEntity(entity); - } - } - } - - return reqObj; - } - -} diff --git a/src/main/java/com/mashape/unirest/http/HttpDeleteWithBody.java b/src/main/java/com/mashape/unirest/http/HttpDeleteWithBody.java deleted file mode 100644 index e5e3a5886..000000000 --- a/src/main/java/com/mashape/unirest/http/HttpDeleteWithBody.java +++ /dev/null @@ -1,52 +0,0 @@ -/* -The MIT License - -Copyright (c) 2013 Mashape (http://mashape.com) - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.mashape.unirest.http; - -import java.net.URI; - -import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; - -class HttpDeleteWithBody extends HttpEntityEnclosingRequestBase { - public static final String METHOD_NAME = "DELETE"; - - public String getMethod() { - return METHOD_NAME; - } - - public HttpDeleteWithBody(final String uri) { - super(); - setURI(URI.create(uri)); - } - - public HttpDeleteWithBody(final URI uri) { - super(); - setURI(uri); - } - - public HttpDeleteWithBody() { - super(); - } -} diff --git a/src/main/java/com/mashape/unirest/http/HttpPatchWithBody.java b/src/main/java/com/mashape/unirest/http/HttpPatchWithBody.java deleted file mode 100644 index 0dad5d372..000000000 --- a/src/main/java/com/mashape/unirest/http/HttpPatchWithBody.java +++ /dev/null @@ -1,52 +0,0 @@ -/* -The MIT License - -Copyright (c) 2013 Mashape (http://mashape.com) - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.mashape.unirest.http; - -import java.net.URI; - -import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; - -class HttpPatchWithBody extends HttpEntityEnclosingRequestBase { - public static final String METHOD_NAME = "PATCH"; - - public String getMethod() { - return METHOD_NAME; - } - - public HttpPatchWithBody(final String uri) { - super(); - setURI(URI.create(uri)); - } - - public HttpPatchWithBody(final URI uri) { - super(); - setURI(uri); - } - - public HttpPatchWithBody() { - super(); - } -} diff --git a/src/main/java/com/mashape/unirest/http/HttpResponse.java b/src/main/java/com/mashape/unirest/http/HttpResponse.java deleted file mode 100644 index e0800d227..000000000 --- a/src/main/java/com/mashape/unirest/http/HttpResponse.java +++ /dev/null @@ -1,136 +0,0 @@ -/* -The MIT License - -Copyright (c) 2013 Mashape (http://mashape.com) - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.mashape.unirest.http; - -import com.mashape.unirest.http.options.Option; -import com.mashape.unirest.http.options.Options; -import com.mashape.unirest.http.utils.ResponseUtils; -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.StatusLine; -import org.apache.http.util.EntityUtils; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.zip.GZIPInputStream; - -public class HttpResponse { - - private int statusCode; - private String statusText; - private Headers headers = new Headers(); - private InputStream rawBody; - private T body; - - @SuppressWarnings("unchecked") - public HttpResponse(org.apache.http.HttpResponse response, Class responseClass) { - HttpEntity responseEntity = response.getEntity(); - ObjectMapper objectMapper = (ObjectMapper) Options.getOption(Option.OBJECT_MAPPER); - - Header[] allHeaders = response.getAllHeaders(); - for (Header header : allHeaders) { - String headerName = header.getName(); - List list = headers.get(headerName); - if (list == null) - list = new ArrayList(); - list.add(header.getValue()); - headers.put(headerName, list); - } - StatusLine statusLine = response.getStatusLine(); - this.statusCode = statusLine.getStatusCode(); - this.statusText = statusLine.getReasonPhrase(); - - if (responseEntity != null) { - String charset = "UTF-8"; - - Header contentType = responseEntity.getContentType(); - if (contentType != null) { - String responseCharset = ResponseUtils.getCharsetFromContentType(contentType.getValue()); - if (responseCharset != null && !responseCharset.trim().equals("")) { - charset = responseCharset; - } - } - - try { - byte[] rawBody; - try { - InputStream responseInputStream = responseEntity.getContent(); - if (ResponseUtils.isGzipped(responseEntity.getContentEncoding())) { - responseInputStream = new GZIPInputStream(responseEntity.getContent()); - } - rawBody = ResponseUtils.getBytes(responseInputStream); - } catch (IOException e2) { - throw new RuntimeException(e2); - } - this.rawBody = new ByteArrayInputStream(rawBody); - - if (JsonNode.class.equals(responseClass)) { - String jsonString = new String(rawBody, charset).trim(); - this.body = (T) new JsonNode(jsonString); - } else if (String.class.equals(responseClass)) { - this.body = (T) new String(rawBody, charset); - } else if (InputStream.class.equals(responseClass)) { - this.body = (T) this.rawBody; - } else if (objectMapper != null) { - this.body = objectMapper.readValue(new String(rawBody, charset), responseClass); - } else { - throw new Exception("Only String, JsonNode and InputStream are supported, or an ObjectMapper implementation is required."); - } - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - EntityUtils.consumeQuietly(responseEntity); - } - - public int getStatus() { - return statusCode; - } - - public String getStatusText() { - return statusText; - } - - /** - * @return Response Headers (map) with same case as server response. - * For instance use getHeaders().getFirst("Location") and not getHeaders().getFirst("location") to get first header "Location" - */ - public Headers getHeaders() { - return headers; - } - - public InputStream getRawBody() { - return rawBody; - } - - public T getBody() { - return body; - } -} diff --git a/src/main/java/com/mashape/unirest/http/JsonNode.java b/src/main/java/com/mashape/unirest/http/JsonNode.java deleted file mode 100644 index b5669b58c..000000000 --- a/src/main/java/com/mashape/unirest/http/JsonNode.java +++ /dev/null @@ -1,85 +0,0 @@ -/* -The MIT License - -Copyright (c) 2013 Mashape (http://mashape.com) - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.mashape.unirest.http; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -public class JsonNode { - - private JSONObject jsonObject; - private JSONArray jsonArray; - - private boolean array; - - public JsonNode(String json) { - if (json == null || "".equals(json.trim())) { - jsonObject = new JSONObject(); - } else { - try { - jsonObject = new JSONObject(json); - } catch (JSONException e) { - // It may be an array - try { - jsonArray = new JSONArray(json); - array = true; - } catch (JSONException e1) { - throw new RuntimeException(e1); - } - } - } - } - - public JSONObject getObject() { - return this.jsonObject; - } - - public JSONArray getArray() { - JSONArray result = this.jsonArray; - if (array == false) { - result = new JSONArray(); - result.put(jsonObject); - } - return result; - } - - public boolean isArray() { - return this.array; - } - - @Override - public String toString() { - if (isArray()) { - if (jsonArray == null) - return null; - return jsonArray.toString(); - } - if (jsonObject == null) - return null; - return jsonObject.toString(); - } -} diff --git a/src/main/java/com/mashape/unirest/http/ObjectMapper.java b/src/main/java/com/mashape/unirest/http/ObjectMapper.java deleted file mode 100644 index a3b1eb36f..000000000 --- a/src/main/java/com/mashape/unirest/http/ObjectMapper.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.mashape.unirest.http; - -public interface ObjectMapper { - - T readValue(String value, Class valueType); - - String writeValue(Object value); -} diff --git a/src/main/java/com/mashape/unirest/http/Unirest.java b/src/main/java/com/mashape/unirest/http/Unirest.java deleted file mode 100644 index 6aba57782..000000000 --- a/src/main/java/com/mashape/unirest/http/Unirest.java +++ /dev/null @@ -1,186 +0,0 @@ -/* -The MIT License - -Copyright (c) 2013 Mashape (http://mashape.com) - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.mashape.unirest.http; - -import com.mashape.unirest.http.async.utils.AsyncIdleConnectionMonitorThread; -import com.mashape.unirest.http.options.Option; -import com.mashape.unirest.http.options.Options; -import com.mashape.unirest.http.utils.SyncIdleConnectionMonitorThread; -import com.mashape.unirest.request.GetRequest; -import com.mashape.unirest.request.HttpRequestWithBody; -import org.apache.http.HttpHost; -import org.apache.http.client.HttpClient; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; - -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - -public class Unirest { - - /** - * Set the HttpClient implementation to use for every synchronous request - */ - public static void setHttpClient(HttpClient httpClient) { - Options.setOption(Option.HTTPCLIENT, httpClient); - Options.customClientSet(); - } - - /** - * Set the asynchronous AbstractHttpAsyncClient implementation to use for every asynchronous request - */ - public static void setAsyncHttpClient(CloseableHttpAsyncClient asyncHttpClient) { - Options.setOption(Option.ASYNCHTTPCLIENT, asyncHttpClient); - Options.customClientSet(); - } - - /** - * Set a proxy - */ - public static void setProxy(HttpHost proxy) { - Options.setOption(Option.PROXY, proxy); - - // Reload the client implementations - Options.refresh(); - } - - /** - * Set the ObjectMapper implementation to use for Response to Object binding - * - * @param objectMapper Custom implementation of ObjectMapper interface - */ - public static void setObjectMapper(ObjectMapper objectMapper) { - Options.setOption(Option.OBJECT_MAPPER, objectMapper); - - // Reload the client implementations - Options.refresh(); - } - - /** - * Set the connection timeout and socket timeout - * - * @param connectionTimeout The timeout until a connection with the server is established (in milliseconds). Default is 10000. Set to zero to disable the timeout. - * @param socketTimeout The timeout to receive data (in milliseconds). Default is 60000. Set to zero to disable the timeout. - */ - public static void setTimeouts(long connectionTimeout, long socketTimeout) { - Options.setOption(Option.CONNECTION_TIMEOUT, connectionTimeout); - Options.setOption(Option.SOCKET_TIMEOUT, socketTimeout); - - // Reload the client implementations - Options.refresh(); - } - - /** - * Set the concurrency levels - * - * @param maxTotal Defines the overall connection limit for a connection pool. Default is 200. - * @param maxPerRoute Defines a connection limit per one HTTP route (this can be considered a per target host limit). Default is 20. - */ - public static void setConcurrency(int maxTotal, int maxPerRoute) { - Options.setOption(Option.MAX_TOTAL, maxTotal); - Options.setOption(Option.MAX_PER_ROUTE, maxPerRoute); - - // Reload the client implementations - Options.refresh(); - } - - /** - * Clear default headers - */ - public static void clearDefaultHeaders() { - Options.setOption(Option.DEFAULT_HEADERS, null); - } - - /** - * Set default header - */ - @SuppressWarnings("unchecked") - public static void setDefaultHeader(String name, String value) { - Object headers = Options.getOption(Option.DEFAULT_HEADERS); - if (headers == null) { - headers = new HashMap(); - } - ((Map) headers).put(name, value); - Options.setOption(Option.DEFAULT_HEADERS, headers); - } - - /** - * Close the asynchronous client and its event loop. Use this method to close all the threads and allow an application to exit. - */ - public static void shutdown() throws IOException { - // Closing the Sync HTTP client - CloseableHttpClient syncClient = (CloseableHttpClient) Options.getOption(Option.HTTPCLIENT); - if (syncClient != null) { - syncClient.close(); - } - - SyncIdleConnectionMonitorThread syncIdleConnectionMonitorThread = (SyncIdleConnectionMonitorThread) Options.getOption(Option.SYNC_MONITOR); - if (syncIdleConnectionMonitorThread != null) { - syncIdleConnectionMonitorThread.interrupt(); - } - - // Closing the Async HTTP client (if running) - CloseableHttpAsyncClient asyncClient = (CloseableHttpAsyncClient) Options.getOption(Option.ASYNCHTTPCLIENT); - if (asyncClient != null && asyncClient.isRunning()) { - asyncClient.close(); - } - - AsyncIdleConnectionMonitorThread asyncMonitorThread = (AsyncIdleConnectionMonitorThread) Options.getOption(Option.ASYNC_MONITOR); - if (asyncMonitorThread != null) { - asyncMonitorThread.interrupt(); - } - } - - public static GetRequest get(String url) { - return new GetRequest(HttpMethod.GET, url); - } - - public static GetRequest head(String url) { - return new GetRequest(HttpMethod.HEAD, url); - } - - public static HttpRequestWithBody options(String url) { - return new HttpRequestWithBody(HttpMethod.OPTIONS, url); - } - - public static HttpRequestWithBody post(String url) { - return new HttpRequestWithBody(HttpMethod.POST, url); - } - - public static HttpRequestWithBody delete(String url) { - return new HttpRequestWithBody(HttpMethod.DELETE, url); - } - - public static HttpRequestWithBody patch(String url) { - return new HttpRequestWithBody(HttpMethod.PATCH, url); - } - - public static HttpRequestWithBody put(String url) { - return new HttpRequestWithBody(HttpMethod.PUT, url); - } - -} diff --git a/src/main/java/com/mashape/unirest/http/async/Callback.java b/src/main/java/com/mashape/unirest/http/async/Callback.java deleted file mode 100644 index 597135b67..000000000 --- a/src/main/java/com/mashape/unirest/http/async/Callback.java +++ /dev/null @@ -1,38 +0,0 @@ -/* -The MIT License - -Copyright (c) 2013 Mashape (http://mashape.com) - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.mashape.unirest.http.async; - -import com.mashape.unirest.http.HttpResponse; -import com.mashape.unirest.http.exceptions.UnirestException; - -public interface Callback { - - void completed(HttpResponse response); - - void failed(UnirestException e); - - void cancelled(); -} diff --git a/src/main/java/com/mashape/unirest/http/async/RequestThread.java b/src/main/java/com/mashape/unirest/http/async/RequestThread.java deleted file mode 100644 index 2e6e96569..000000000 --- a/src/main/java/com/mashape/unirest/http/async/RequestThread.java +++ /dev/null @@ -1,58 +0,0 @@ -/* -The MIT License - -Copyright (c) 2013 Mashape (http://mashape.com) - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.mashape.unirest.http.async; - -import com.mashape.unirest.http.HttpClientHelper; -import com.mashape.unirest.http.HttpResponse; -import com.mashape.unirest.http.exceptions.UnirestException; -import com.mashape.unirest.request.HttpRequest; - -public class RequestThread extends Thread { - - private HttpRequest httpRequest; - private Class responseClass; - private Callback callback; - - public RequestThread(HttpRequest httpRequest, Class responseClass, Callback callback) { - this.httpRequest = httpRequest; - this.responseClass = responseClass; - this.callback = callback; - } - - @Override - public void run() { - HttpResponse response; - try { - response = HttpClientHelper.request(httpRequest, responseClass); - if (callback != null) { - callback.completed(response); - } - } catch (UnirestException e) { - callback.failed(e); - } - } - -} diff --git a/src/main/java/com/mashape/unirest/http/async/utils/AsyncIdleConnectionMonitorThread.java b/src/main/java/com/mashape/unirest/http/async/utils/AsyncIdleConnectionMonitorThread.java deleted file mode 100644 index 3f899c017..000000000 --- a/src/main/java/com/mashape/unirest/http/async/utils/AsyncIdleConnectionMonitorThread.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.mashape.unirest.http.async.utils; - -import java.util.concurrent.TimeUnit; - -import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager; - -public class AsyncIdleConnectionMonitorThread extends Thread { - - private final PoolingNHttpClientConnectionManager connMgr; - - public AsyncIdleConnectionMonitorThread(PoolingNHttpClientConnectionManager connMgr) { - super(); - super.setDaemon(true); - this.connMgr = connMgr; - } - - @Override - public void run() { - try { - while (!Thread.currentThread().isInterrupted()) { - synchronized (this) { - wait(5000); - // Close expired connections - connMgr.closeExpiredConnections(); - // Optionally, close connections - // that have been idle longer than 30 sec - connMgr.closeIdleConnections(30, TimeUnit.SECONDS); - } - } - } catch (InterruptedException ex) { - // terminate - } - } - -} \ No newline at end of file diff --git a/src/main/java/com/mashape/unirest/http/exceptions/UnirestException.java b/src/main/java/com/mashape/unirest/http/exceptions/UnirestException.java deleted file mode 100644 index 50b13957c..000000000 --- a/src/main/java/com/mashape/unirest/http/exceptions/UnirestException.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.mashape.unirest.http.exceptions; - -public class UnirestException extends Exception { - - private static final long serialVersionUID = -3714840499934575734L; - - public UnirestException(Exception e) { - super(e); - } - - public UnirestException(String msg) { - super(msg); - } - -} diff --git a/src/main/java/com/mashape/unirest/http/options/Option.java b/src/main/java/com/mashape/unirest/http/options/Option.java deleted file mode 100644 index a146ce2b1..000000000 --- a/src/main/java/com/mashape/unirest/http/options/Option.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.mashape.unirest.http.options; - -public enum Option { - HTTPCLIENT, ASYNCHTTPCLIENT, CONNECTION_TIMEOUT, SOCKET_TIMEOUT, DEFAULT_HEADERS, SYNC_MONITOR, ASYNC_MONITOR, MAX_TOTAL, MAX_PER_ROUTE, PROXY, OBJECT_MAPPER -} diff --git a/src/main/java/com/mashape/unirest/http/options/Options.java b/src/main/java/com/mashape/unirest/http/options/Options.java deleted file mode 100644 index 8c73de19e..000000000 --- a/src/main/java/com/mashape/unirest/http/options/Options.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.mashape.unirest.http.options; - -import java.util.HashMap; -import java.util.Map; - -import org.apache.http.HttpHost; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; -import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; -import org.apache.http.impl.nio.client.HttpAsyncClientBuilder; -import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager; -import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor; -import org.apache.http.nio.reactor.IOReactorException; - -import com.mashape.unirest.http.async.utils.AsyncIdleConnectionMonitorThread; -import com.mashape.unirest.http.utils.SyncIdleConnectionMonitorThread; - -public class Options { - - public static final long CONNECTION_TIMEOUT = 10000; - private static final long SOCKET_TIMEOUT = 60000; - public static final int MAX_TOTAL = 200; - public static final int MAX_PER_ROUTE = 20; - - private static Map options = new HashMap(); - - private static boolean customClientSet = false; - - public static void customClientSet() { - customClientSet = true; - } - - public static void setOption(Option option, Object value) { - if ((option == Option.CONNECTION_TIMEOUT || option == Option.SOCKET_TIMEOUT) && customClientSet) { - throw new RuntimeException("You can't set custom timeouts when providing custom client implementations. Set the timeouts directly in your custom client configuration instead."); - } - options.put(option, value); - } - - public static Object getOption(Option option) { - return options.get(option); - } - - static { - refresh(); - } - - public static void refresh() { - // Load timeouts - Object connectionTimeout = Options.getOption(Option.CONNECTION_TIMEOUT); - if (connectionTimeout == null) - connectionTimeout = CONNECTION_TIMEOUT; - Object socketTimeout = Options.getOption(Option.SOCKET_TIMEOUT); - if (socketTimeout == null) - socketTimeout = SOCKET_TIMEOUT; - - // Load limits - Object maxTotal = Options.getOption(Option.MAX_TOTAL); - if (maxTotal == null) - maxTotal = MAX_TOTAL; - Object maxPerRoute = Options.getOption(Option.MAX_PER_ROUTE); - if (maxPerRoute == null) - maxPerRoute = MAX_PER_ROUTE; - - // Load proxy if set - HttpHost proxy = (HttpHost) Options.getOption(Option.PROXY); - - // Create common default configuration - RequestConfig clientConfig = RequestConfig.custom().setConnectTimeout(((Long) connectionTimeout).intValue()).setSocketTimeout(((Long) socketTimeout).intValue()).setConnectionRequestTimeout(((Long) socketTimeout).intValue()).setProxy(proxy).build(); - - PoolingHttpClientConnectionManager syncConnectionManager = new PoolingHttpClientConnectionManager(); - syncConnectionManager.setMaxTotal((Integer) maxTotal); - syncConnectionManager.setDefaultMaxPerRoute((Integer) maxPerRoute); - - // Create clients - setOption(Option.HTTPCLIENT, HttpClientBuilder.create().setDefaultRequestConfig(clientConfig).setConnectionManager(syncConnectionManager).build()); - SyncIdleConnectionMonitorThread syncIdleConnectionMonitorThread = new SyncIdleConnectionMonitorThread(syncConnectionManager); - setOption(Option.SYNC_MONITOR, syncIdleConnectionMonitorThread); - syncIdleConnectionMonitorThread.start(); - - DefaultConnectingIOReactor ioreactor; - PoolingNHttpClientConnectionManager asyncConnectionManager; - try { - ioreactor = new DefaultConnectingIOReactor(); - asyncConnectionManager = new PoolingNHttpClientConnectionManager(ioreactor); - asyncConnectionManager.setMaxTotal((Integer) maxTotal); - asyncConnectionManager.setDefaultMaxPerRoute((Integer) maxPerRoute); - } catch (IOReactorException e) { - throw new RuntimeException(e); - } - - CloseableHttpAsyncClient asyncClient = HttpAsyncClientBuilder.create().setDefaultRequestConfig(clientConfig).setConnectionManager(asyncConnectionManager).build(); - setOption(Option.ASYNCHTTPCLIENT, asyncClient); - setOption(Option.ASYNC_MONITOR, new AsyncIdleConnectionMonitorThread(asyncConnectionManager)); - } - -} diff --git a/src/main/java/com/mashape/unirest/http/utils/Base64Coder.java b/src/main/java/com/mashape/unirest/http/utils/Base64Coder.java deleted file mode 100644 index ad8378d6e..000000000 --- a/src/main/java/com/mashape/unirest/http/utils/Base64Coder.java +++ /dev/null @@ -1,260 +0,0 @@ -package com.mashape.unirest.http.utils; - -//Copyright 2003-2010 Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland -//www.source-code.biz, www.inventec.ch/chdh -// -//This module is multi-licensed and may be used under the terms -//of any of the following licenses: -// -//EPL, Eclipse Public License, V1.0 or later, http://www.eclipse.org/legal -//LGPL, GNU Lesser General Public License, V2.1 or later, http://www.gnu.org/licenses/lgpl.html -//GPL, GNU General Public License, V2 or later, http://www.gnu.org/licenses/gpl.html -//AL, Apache License, V2.0 or later, http://www.apache.org/licenses -//BSD, BSD License, http://www.opensource.org/licenses/bsd-license.php -//MIT, MIT License, http://www.opensource.org/licenses/MIT -// -//Please contact the author if you need another license. -//This module is provided "as is", without warranties of any kind. - -/** - * A Base64 encoder/decoder. - * - *

- * This class is used to encode and decode data in Base64 format as described in RFC 1521. - * - *

- * Project home page: www.source-code.biz/base64coder/java
- * Author: Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland
- * Multi-licensed: EPL / LGPL / GPL / AL / BSD / MIT. - */ -public class Base64Coder { - - // The line separator string of the operating system. - private static final String systemLineSeparator = System.getProperty("line.separator"); - - // Mapping table from 6-bit nibbles to Base64 characters. - private static final char[] map1 = new char[64]; - static { - int i = 0; - for (char c = 'A'; c <= 'Z'; c++) - map1[i++] = c; - for (char c = 'a'; c <= 'z'; c++) - map1[i++] = c; - for (char c = '0'; c <= '9'; c++) - map1[i++] = c; - map1[i++] = '+'; - map1[i++] = '/'; - } - - // Mapping table from Base64 characters to 6-bit nibbles. - private static final byte[] map2 = new byte[128]; - static { - for (int i = 0; i < map2.length; i++) - map2[i] = -1; - for (int i = 0; i < 64; i++) - map2[map1[i]] = (byte) i; - } - - /** - * Encodes a string into Base64 format. No blanks or line breaks are inserted. - * - * @param s A String to be encoded. - * @return A String containing the Base64 encoded data. - */ - public static String encodeString(String s) { - return new String(encode(s.getBytes())); - } - - /** - * Encodes a byte array into Base 64 format and breaks the output into lines of 76 characters. This method is compatible with - * sun.misc.BASE64Encoder.encodeBuffer(byte[]). - * - * @param in An array containing the data bytes to be encoded. - * @return A String containing the Base64 encoded data, broken into lines. - */ - public static String encodeLines(byte[] in) { - return encodeLines(in, 0, in.length, 76, systemLineSeparator); - } - - /** - * Encodes a byte array into Base 64 format and breaks the output into lines. - * - * @param in An array containing the data bytes to be encoded. - * @param iOff Offset of the first byte in in to be processed. - * @param iLen Number of bytes to be processed in in, starting at iOff. - * @param lineLen Line length for the output data. Should be a multiple of 4. - * @param lineSeparator The line separator to be used to separate the output lines. - * @return A String containing the Base64 encoded data, broken into lines. - */ - public static String encodeLines(byte[] in, int iOff, int iLen, int lineLen, String lineSeparator) { - int blockLen = (lineLen * 3) / 4; - if (blockLen <= 0) - throw new IllegalArgumentException(); - int lines = (iLen + blockLen - 1) / blockLen; - int bufLen = ((iLen + 2) / 3) * 4 + lines * lineSeparator.length(); - StringBuilder buf = new StringBuilder(bufLen); - int ip = 0; - while (ip < iLen) { - int l = Math.min(iLen - ip, blockLen); - buf.append(encode(in, iOff + ip, l)); - buf.append(lineSeparator); - ip += l; - } - return buf.toString(); - } - - /** - * Encodes a byte array into Base64 format. No blanks or line breaks are inserted in the output. - * - * @param in An array containing the data bytes to be encoded. - * @return A character array containing the Base64 encoded data. - */ - public static char[] encode(byte[] in) { - return encode(in, 0, in.length); - } - - /** - * Encodes a byte array into Base64 format. No blanks or line breaks are inserted in the output. - * - * @param in An array containing the data bytes to be encoded. - * @param iLen Number of bytes to process in in. - * @return A character array containing the Base64 encoded data. - */ - public static char[] encode(byte[] in, int iLen) { - return encode(in, 0, iLen); - } - - /** - * Encodes a byte array into Base64 format. No blanks or line breaks are inserted in the output. - * - * @param in An array containing the data bytes to be encoded. - * @param iOff Offset of the first byte in in to be processed. - * @param iLen Number of bytes to process in in, starting at iOff. - * @return A character array containing the Base64 encoded data. - */ - public static char[] encode(byte[] in, int iOff, int iLen) { - int oDataLen = (iLen * 4 + 2) / 3; // output length without padding - int oLen = ((iLen + 2) / 3) * 4; // output length including padding - char[] out = new char[oLen]; - int ip = iOff; - int iEnd = iOff + iLen; - int op = 0; - while (ip < iEnd) { - int i0 = in[ip++] & 0xff; - int i1 = ip < iEnd ? in[ip++] & 0xff : 0; - int i2 = ip < iEnd ? in[ip++] & 0xff : 0; - int o0 = i0 >>> 2; - int o1 = ((i0 & 3) << 4) | (i1 >>> 4); - int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6); - int o3 = i2 & 0x3F; - out[op++] = map1[o0]; - out[op++] = map1[o1]; - out[op] = op < oDataLen ? map1[o2] : '='; - op++; - out[op] = op < oDataLen ? map1[o3] : '='; - op++; - } - return out; - } - - /** - * Decodes a string from Base64 format. No blanks or line breaks are allowed within the Base64 encoded input data. - * - * @param s A Base64 String to be decoded. - * @return A String containing the decoded data. - * @throws IllegalArgumentException If the input is not valid Base64 encoded data. - */ - public static String decodeString(String s) { - return new String(decode(s)); - } - - /** - * Decodes a byte array from Base64 format and ignores line separators, tabs and blanks. CR, LF, Tab and Space characters are ignored in the input data. This method is - * compatible with sun.misc.BASE64Decoder.decodeBuffer(String). - * - * @param s A Base64 String to be decoded. - * @return An array containing the decoded data bytes. - * @throws IllegalArgumentException If the input is not valid Base64 encoded data. - */ - public static byte[] decodeLines(String s) { - char[] buf = new char[s.length()]; - int p = 0; - for (int ip = 0; ip < s.length(); ip++) { - char c = s.charAt(ip); - if (c != ' ' && c != '\r' && c != '\n' && c != '\t') - buf[p++] = c; - } - return decode(buf, 0, p); - } - - /** - * Decodes a byte array from Base64 format. No blanks or line breaks are allowed within the Base64 encoded input data. - * - * @param s A Base64 String to be decoded. - * @return An array containing the decoded data bytes. - * @throws IllegalArgumentException If the input is not valid Base64 encoded data. - */ - public static byte[] decode(String s) { - return decode(s.toCharArray()); - } - - /** - * Decodes a byte array from Base64 format. No blanks or line breaks are allowed within the Base64 encoded input data. - * - * @param in A character array containing the Base64 encoded data. - * @return An array containing the decoded data bytes. - * @throws IllegalArgumentException If the input is not valid Base64 encoded data. - */ - public static byte[] decode(char[] in) { - return decode(in, 0, in.length); - } - - /** - * Decodes a byte array from Base64 format. No blanks or line breaks are allowed within the Base64 encoded input data. - * - * @param in A character array containing the Base64 encoded data. - * @param iOff Offset of the first character in in to be processed. - * @param iLen Number of characters to process in in, starting at iOff. - * @return An array containing the decoded data bytes. - * @throws IllegalArgumentException If the input is not valid Base64 encoded data. - */ - public static byte[] decode(char[] in, int iOff, int iLen) { - if (iLen % 4 != 0) - throw new IllegalArgumentException("Length of Base64 encoded input string is not a multiple of 4."); - while (iLen > 0 && in[iOff + iLen - 1] == '=') - iLen--; - int oLen = (iLen * 3) / 4; - byte[] out = new byte[oLen]; - int ip = iOff; - int iEnd = iOff + iLen; - int op = 0; - while (ip < iEnd) { - int i0 = in[ip++]; - int i1 = in[ip++]; - int i2 = ip < iEnd ? in[ip++] : 'A'; - int i3 = ip < iEnd ? in[ip++] : 'A'; - if (i0 > 127 || i1 > 127 || i2 > 127 || i3 > 127) - throw new IllegalArgumentException("Illegal character in Base64 encoded data."); - int b0 = map2[i0]; - int b1 = map2[i1]; - int b2 = map2[i2]; - int b3 = map2[i3]; - if (b0 < 0 || b1 < 0 || b2 < 0 || b3 < 0) - throw new IllegalArgumentException("Illegal character in Base64 encoded data."); - int o0 = (b0 << 2) | (b1 >>> 4); - int o1 = ((b1 & 0xf) << 4) | (b2 >>> 2); - int o2 = ((b2 & 3) << 6) | b3; - out[op++] = (byte) o0; - if (op < oLen) - out[op++] = (byte) o1; - if (op < oLen) - out[op++] = (byte) o2; - } - return out; - } - - // Dummy constructor. - private Base64Coder() { - } - -} // end class Base64Coder \ No newline at end of file diff --git a/src/main/java/com/mashape/unirest/http/utils/ClientFactory.java b/src/main/java/com/mashape/unirest/http/utils/ClientFactory.java deleted file mode 100644 index a4167a89a..000000000 --- a/src/main/java/com/mashape/unirest/http/utils/ClientFactory.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.mashape.unirest.http.utils; - -import org.apache.http.client.HttpClient; -import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; - -import com.mashape.unirest.http.options.Option; -import com.mashape.unirest.http.options.Options; - -public class ClientFactory { - - public static HttpClient getHttpClient() { - return (HttpClient) Options.getOption(Option.HTTPCLIENT); - } - - public static CloseableHttpAsyncClient getAsyncHttpClient() { - return (CloseableHttpAsyncClient) Options.getOption(Option.ASYNCHTTPCLIENT); - } - -} diff --git a/src/main/java/com/mashape/unirest/http/utils/MapUtil.java b/src/main/java/com/mashape/unirest/http/utils/MapUtil.java deleted file mode 100644 index 37edd7783..000000000 --- a/src/main/java/com/mashape/unirest/http/utils/MapUtil.java +++ /dev/null @@ -1,57 +0,0 @@ -/* -The MIT License - -Copyright (c) 2013 Mashape (http://mashape.com) - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.mashape.unirest.http.utils; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.TreeMap; - -import org.apache.http.NameValuePair; -import org.apache.http.message.BasicNameValuePair; - -public class MapUtil { - - public static List getList(Map> parameters) { - List result = new ArrayList(); - if (parameters != null) { - TreeMap> sortedParameters = new TreeMap>(parameters); - for (Entry> entry : sortedParameters.entrySet()) { - List entryValue = entry.getValue(); - if (entryValue != null) { - for (Object cur : entryValue) { - if (cur != null) { - result.add(new BasicNameValuePair(entry.getKey(), cur.toString())); - } - } - } - } - } - return result; - } - -} diff --git a/src/main/java/com/mashape/unirest/http/utils/ResponseUtils.java b/src/main/java/com/mashape/unirest/http/utils/ResponseUtils.java deleted file mode 100644 index 7fb7d0891..000000000 --- a/src/main/java/com/mashape/unirest/http/utils/ResponseUtils.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.mashape.unirest.http.utils; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.apache.http.Header; - -public class ResponseUtils { - - private static final Pattern charsetPattern = Pattern.compile("(?i)\\bcharset=\\s*\"?([^\\s;\"]*)"); - - /** - * Parse out a charset from a content type header. - * - * @param contentType e.g. "text/html; charset=EUC-JP" - * @return "EUC-JP", or null if not found. Charset is trimmed and uppercased. - */ - public static String getCharsetFromContentType(String contentType) { - if (contentType == null) - return null; - - Matcher m = charsetPattern.matcher(contentType); - if (m.find()) { - return m.group(1).trim().toUpperCase(); - } - return null; - } - - public static byte[] getBytes(InputStream is) throws IOException { - int len; - int size = 1024; - byte[] buf; - - if (is instanceof ByteArrayInputStream) { - size = is.available(); - buf = new byte[size]; - len = is.read(buf, 0, size); - } else { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - buf = new byte[size]; - while ((len = is.read(buf, 0, size)) != -1) - bos.write(buf, 0, len); - buf = bos.toByteArray(); - } - return buf; - } - - public static boolean isGzipped(Header contentEncoding) { - if (contentEncoding != null) { - String value = contentEncoding.getValue(); - if (value != null && "gzip".equals(value.toLowerCase().trim())) { - return true; - } - } - return false; - } - -} diff --git a/src/main/java/com/mashape/unirest/http/utils/SyncIdleConnectionMonitorThread.java b/src/main/java/com/mashape/unirest/http/utils/SyncIdleConnectionMonitorThread.java deleted file mode 100644 index 05ffc7b86..000000000 --- a/src/main/java/com/mashape/unirest/http/utils/SyncIdleConnectionMonitorThread.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.mashape.unirest.http.utils; - -import java.util.concurrent.TimeUnit; - -import org.apache.http.conn.HttpClientConnectionManager; - -public class SyncIdleConnectionMonitorThread extends Thread { - - private final HttpClientConnectionManager connMgr; - - public SyncIdleConnectionMonitorThread(HttpClientConnectionManager connMgr) { - super(); - super.setDaemon(true); - this.connMgr = connMgr; - } - - @Override - public void run() { - try { - while (!Thread.currentThread().isInterrupted()) { - synchronized (this) { - wait(5000); - // Close expired connections - connMgr.closeExpiredConnections(); - // Optionally, close connections - // that have been idle longer than 30 sec - connMgr.closeIdleConnections(30, TimeUnit.SECONDS); - } - } - } catch (InterruptedException ex) { - // terminate - } - } - -} \ No newline at end of file diff --git a/src/main/java/com/mashape/unirest/http/utils/URLParamEncoder.java b/src/main/java/com/mashape/unirest/http/utils/URLParamEncoder.java deleted file mode 100644 index c636644b6..000000000 --- a/src/main/java/com/mashape/unirest/http/utils/URLParamEncoder.java +++ /dev/null @@ -1,54 +0,0 @@ -/* -The MIT License - -Copyright (c) 2013 Mashape (http://mashape.com) - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.mashape.unirest.http.utils; - -public class URLParamEncoder { - - public static String encode(String input) { - StringBuilder resultStr = new StringBuilder(); - for (char ch : input.toCharArray()) { - if (isUnsafe(ch)) { - resultStr.append('%'); - resultStr.append(toHex(ch / 16)); - resultStr.append(toHex(ch % 16)); - } else { - resultStr.append(ch); - } - } - return resultStr.toString(); - } - - private static char toHex(int ch) { - return (char) (ch < 10 ? '0' + ch : 'A' + ch - 10); - } - - private static boolean isUnsafe(char ch) { - if (ch > 128 || ch < 0) - return true; - return " %$&+,/:;=?@<>#%".indexOf(ch) >= 0; - } - -} \ No newline at end of file diff --git a/src/main/java/com/mashape/unirest/request/BaseRequest.java b/src/main/java/com/mashape/unirest/request/BaseRequest.java deleted file mode 100644 index a19af21e7..000000000 --- a/src/main/java/com/mashape/unirest/request/BaseRequest.java +++ /dev/null @@ -1,103 +0,0 @@ -/* -The MIT License - -Copyright (c) 2013 Mashape (http://mashape.com) - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.mashape.unirest.request; - -import com.mashape.unirest.http.HttpClientHelper; -import com.mashape.unirest.http.HttpResponse; -import com.mashape.unirest.http.JsonNode; -import com.mashape.unirest.http.async.Callback; -import com.mashape.unirest.http.exceptions.UnirestException; - -import java.io.InputStream; -import java.util.concurrent.Future; - -public abstract class BaseRequest { - - protected static final String UTF_8 = "UTF-8"; - - protected HttpRequest httpRequest; - - protected BaseRequest(HttpRequest httpRequest) { - this.httpRequest = httpRequest; - } - - public HttpRequest getHttpRequest() { - return this.httpRequest; - } - - protected BaseRequest() { - super(); - } - - public HttpResponse asString() throws UnirestException { - return HttpClientHelper.request(httpRequest, String.class); - } - - public Future> asStringAsync() { - return HttpClientHelper.requestAsync(httpRequest, String.class, null); - } - - public Future> asStringAsync(Callback callback) { - return HttpClientHelper.requestAsync(httpRequest, String.class, callback); - } - - public HttpResponse asJson() throws UnirestException { - return HttpClientHelper.request(httpRequest, JsonNode.class); - } - - public Future> asJsonAsync() { - return HttpClientHelper.requestAsync(httpRequest, JsonNode.class, null); - } - - public Future> asJsonAsync(Callback callback) { - return HttpClientHelper.requestAsync(httpRequest, JsonNode.class, callback); - } - - public HttpResponse asObject(Class responseClass) throws UnirestException { - return HttpClientHelper.request(httpRequest, (Class) responseClass); - } - - public Future> asObjectAsync(Class responseClass) { - return HttpClientHelper.requestAsync(httpRequest, (Class) responseClass, null); - } - - public Future> asObjectAsync(Class responseClass, Callback callback) { - return HttpClientHelper.requestAsync(httpRequest, (Class) responseClass, callback); - } - - public HttpResponse asBinary() throws UnirestException { - return HttpClientHelper.request(httpRequest, InputStream.class); - } - - public Future> asBinaryAsync() { - return HttpClientHelper.requestAsync(httpRequest, InputStream.class, null); - } - - public Future> asBinaryAsync(Callback callback) { - return HttpClientHelper.requestAsync(httpRequest, InputStream.class, callback); - } - -} diff --git a/src/main/java/com/mashape/unirest/request/GetRequest.java b/src/main/java/com/mashape/unirest/request/GetRequest.java deleted file mode 100644 index 1e1471f87..000000000 --- a/src/main/java/com/mashape/unirest/request/GetRequest.java +++ /dev/null @@ -1,58 +0,0 @@ -/* -The MIT License - -Copyright (c) 2013 Mashape (http://mashape.com) - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.mashape.unirest.request; - -import java.util.Map; - -import com.mashape.unirest.http.HttpMethod; - -public class GetRequest extends HttpRequest { - - public GetRequest(HttpMethod method, String url) { - super(method, url); - } - - public GetRequest routeParam(String name, String value) { - super.routeParam(name, value); - return this; - } - - @Override - public GetRequest header(String name, String value) { - return (GetRequest) super.header(name, value); - } - - @Override - public GetRequest headers(Map headers) { - return (GetRequest) super.headers(headers); - } - - @Override - public GetRequest basicAuth(String username, String password) { - super.basicAuth(username, password); - return this; - } -} diff --git a/src/main/java/com/mashape/unirest/request/HttpRequest.java b/src/main/java/com/mashape/unirest/request/HttpRequest.java deleted file mode 100644 index e0bc52f1b..000000000 --- a/src/main/java/com/mashape/unirest/request/HttpRequest.java +++ /dev/null @@ -1,152 +0,0 @@ -/* -The MIT License - -Copyright (c) 2013 Mashape (http://mashape.com) - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.mashape.unirest.request; - -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.TreeMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import com.mashape.unirest.http.HttpMethod; -import com.mashape.unirest.http.utils.Base64Coder; -import com.mashape.unirest.http.utils.URLParamEncoder; -import com.mashape.unirest.request.body.Body; - -public class HttpRequest extends BaseRequest { - - private HttpMethod httpMethod; - protected String url; - Map> headers = new TreeMap>(String.CASE_INSENSITIVE_ORDER); - protected Body body; - - public HttpRequest(HttpMethod method, String url) { - this.httpMethod = method; - this.url = url; - super.httpRequest = this; - } - - public HttpRequest routeParam(String name, String value) { - Matcher matcher = Pattern.compile("\\{" + name + "\\}").matcher(url); - int count = 0; - while (matcher.find()) { - count++; - } - if (count == 0) { - throw new RuntimeException("Can't find route parameter name \"" + name + "\""); - } - this.url = url.replaceAll("\\{" + name + "\\}", URLParamEncoder.encode(value)); - return this; - } - - public HttpRequest basicAuth(String username, String password) { - header("Authorization", "Basic " + Base64Coder.encodeString(username + ":" + password)); - return this; - } - - public HttpRequest header(String name, String value) { - List list = this.headers.get(name.trim()); - if (list == null) { - list = new ArrayList(); - } - list.add(value); - this.headers.put(name.trim(), list); - return this; - } - - public HttpRequest headers(Map headers) { - if (headers != null) { - for (Map.Entry entry : headers.entrySet()) { - header(entry.getKey(), entry.getValue()); - } - } - return this; - } - - public HttpRequest queryString(String name, Collection value) { - for (Object cur : value) { - queryString(name, cur); - } - return this; - } - - public HttpRequest queryString(String name, Object value) { - StringBuilder queryString = new StringBuilder(); - if (this.url.contains("?")) { - queryString.append("&"); - } else { - queryString.append("?"); - } - try { - queryString - .append(URLEncoder.encode(name)) - .append("=") - .append(URLEncoder.encode((value == null) ? "" : value.toString(), "UTF-8")); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - this.url += queryString.toString(); - return this; - } - - public HttpRequest queryString(Map parameters) { - if (parameters != null) { - for (Entry param : parameters.entrySet()) { - if (param.getValue() instanceof String || param.getValue() instanceof Number || param.getValue() instanceof Boolean) { - queryString(param.getKey(), param.getValue()); - } else { - throw new RuntimeException("Parameter \"" + param.getKey() + "\" can't be sent with a GET request because of type: " + param.getValue().getClass().getName()); - } - } - } - return this; - } - - public HttpMethod getHttpMethod() { - return httpMethod; - } - - public String getUrl() { - return url; - } - - public Map> getHeaders() { - if (headers == null) - return new HashMap>(); - return headers; - } - - public Body getBody() { - return body; - } - -} diff --git a/src/main/java/com/mashape/unirest/request/HttpRequestWithBody.java b/src/main/java/com/mashape/unirest/request/HttpRequestWithBody.java deleted file mode 100644 index e3e641434..000000000 --- a/src/main/java/com/mashape/unirest/request/HttpRequestWithBody.java +++ /dev/null @@ -1,186 +0,0 @@ -/* -The MIT License - -Copyright (c) 2013 Mashape (http://mashape.com) - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.mashape.unirest.request; - -import org.apache.http.entity.ContentType; -import org.apache.http.entity.mime.content.InputStreamBody; - -import com.mashape.unirest.http.HttpMethod; -import com.mashape.unirest.http.JsonNode; -import com.mashape.unirest.http.ObjectMapper; -import com.mashape.unirest.http.options.Option; -import com.mashape.unirest.http.options.Options; -import com.mashape.unirest.request.body.MultipartBody; -import com.mashape.unirest.request.body.RawBody; -import com.mashape.unirest.request.body.RequestBodyEntity; - -import org.json.JSONArray; -import org.json.JSONObject; - -import java.io.File; -import java.io.InputStream; -import java.util.Collection; -import java.util.Map; -import java.util.Map.Entry; - -public class HttpRequestWithBody extends HttpRequest { - - public HttpRequestWithBody(HttpMethod method, String url) { - super(method, url); - } - - @Override - public HttpRequestWithBody routeParam(String name, String value) { - super.routeParam(name, value); - return this; - } - - @Override - public HttpRequestWithBody header(String name, String value) { - return (HttpRequestWithBody) super.header(name, value); - } - - @Override - public HttpRequestWithBody headers(Map headers) { - return (HttpRequestWithBody) super.headers(headers); - } - - @Override - public HttpRequestWithBody basicAuth(String username, String password) { - super.basicAuth(username, password); - return this; - } - - @Override - public HttpRequestWithBody queryString(Map parameters) { - return (HttpRequestWithBody) super.queryString(parameters); - } - - @Override - public HttpRequestWithBody queryString(String name, Object value) { - return (HttpRequestWithBody) super.queryString(name, value); - } - - public MultipartBody field(String name, Collection value) { - MultipartBody body = new MultipartBody(this).field(name, value); - this.body = body; - return body; - } - - public MultipartBody field(String name, Object value) { - return field(name, value, null); - } - - public MultipartBody field(String name, File file) { - return field(name, file, null); - } - - public MultipartBody field(String name, Object value, String contentType) { - MultipartBody body = new MultipartBody(this).field(name, (value == null) ? "" : value.toString(), contentType); - this.body = body; - return body; - } - - public MultipartBody field(String name, File file, String contentType) { - MultipartBody body = new MultipartBody(this).field(name, file, contentType); - this.body = body; - return body; - } - - public MultipartBody fields(Map parameters) { - MultipartBody body = new MultipartBody(this); - if (parameters != null) { - for (Entry param : parameters.entrySet()) { - if (param.getValue() instanceof File) { - body.field(param.getKey(), (File) param.getValue()); - } else { - body.field(param.getKey(), (param.getValue() == null) ? "" : param.getValue().toString()); - } - } - } - this.body = body; - return body; - } - - public MultipartBody field(String name, InputStream stream, ContentType contentType, String fileName) { - InputStreamBody inputStreamBody = new InputStreamBody(stream, contentType, fileName); - MultipartBody body = new MultipartBody(this).field(name, inputStreamBody, true, contentType.toString()); - this.body = body; - return body; - } - - public MultipartBody field(String name, InputStream stream, String fileName) { - MultipartBody body = field(name, stream, ContentType.APPLICATION_OCTET_STREAM, fileName); - this.body = body; - return body; - } - - public RequestBodyEntity body(JsonNode body) { - return body(body.toString()); - } - - public RequestBodyEntity body(String body) { - RequestBodyEntity b = new RequestBodyEntity(this).body(body); - this.body = b; - return b; - } - - public RequestBodyEntity body(Object body) { - ObjectMapper objectMapper = (ObjectMapper) Options.getOption(Option.OBJECT_MAPPER); - - if (objectMapper == null) { - throw new RuntimeException("Serialization Impossible. Can't find an ObjectMapper implementation."); - } - - return body(objectMapper.writeValue(body)); - } - - public RawBody body(byte[] body) { - RawBody b = new RawBody(this).body(body); - this.body = b; - return b; - } - - /** - * Sugar method for body operation - * - * @param body raw org.JSONObject - * @return RequestBodyEntity instance - */ - public RequestBodyEntity body(JSONObject body) { - return body(body.toString()); - } - - /** - * Sugar method for body operation - * - * @param body raw org.JSONArray - * @return RequestBodyEntity instance - */ - public RequestBodyEntity body(JSONArray body) { - return body(body.toString()); - } -} \ No newline at end of file diff --git a/src/main/java/com/mashape/unirest/request/body/Body.java b/src/main/java/com/mashape/unirest/request/body/Body.java deleted file mode 100644 index 104d2dd44..000000000 --- a/src/main/java/com/mashape/unirest/request/body/Body.java +++ /dev/null @@ -1,34 +0,0 @@ -/* -The MIT License - -Copyright (c) 2013 Mashape (http://mashape.com) - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.mashape.unirest.request.body; - -import org.apache.http.HttpEntity; - -public interface Body { - - HttpEntity getEntity(); - -} diff --git a/src/main/java/com/mashape/unirest/request/body/MultipartBody.java b/src/main/java/com/mashape/unirest/request/body/MultipartBody.java deleted file mode 100644 index 4d88df1f4..000000000 --- a/src/main/java/com/mashape/unirest/request/body/MultipartBody.java +++ /dev/null @@ -1,181 +0,0 @@ -/* -The MIT License - -Copyright (c) 2013 Mashape (http://mashape.com) - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.mashape.unirest.request.body; - -import java.io.File; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.LinkedHashMap; - -import org.apache.http.HttpEntity; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.mime.HttpMultipartMode; -import org.apache.http.entity.mime.MultipartEntityBuilder; -import org.apache.http.entity.mime.content.ByteArrayBody; -import org.apache.http.entity.mime.content.ContentBody; -import org.apache.http.entity.mime.content.FileBody; -import org.apache.http.entity.mime.content.InputStreamBody; -import org.apache.http.entity.mime.content.StringBody; - -import com.mashape.unirest.http.utils.MapUtil; -import com.mashape.unirest.request.BaseRequest; -import com.mashape.unirest.request.HttpRequest; - -public class MultipartBody extends BaseRequest implements Body { - private Map> parameters = new LinkedHashMap>(); - private Map contentTypes = new HashMap(); - - private boolean hasFile; - private HttpRequest httpRequestObj; - private HttpMultipartMode mode; - - public MultipartBody(HttpRequest httpRequest) { - super(httpRequest); - this.httpRequestObj = httpRequest; - } - - public MultipartBody field(String name, String value) { - return field(name, value, false, null); - } - - public MultipartBody field(String name, String value, String contentType) { - return field(name, value, false, contentType); - } - - public MultipartBody field(String name, Collection collection) { - for (Object current : collection) { - boolean isFile = current instanceof File; - field(name, current, isFile, null); - } - return this; - } - - public MultipartBody field(String name, Object value) { - return field(name, value, false, null); - } - - public MultipartBody field(String name, Object value, boolean file) { - return field(name, value, file, null); - } - - public MultipartBody field(String name, Object value, boolean file, String contentType) { - List list = parameters.get(name); - if (list == null) - list = new LinkedList(); - list.add(value); - parameters.put(name, list); - - ContentType type = null; - if (contentType != null && contentType.length() > 0) { - type = ContentType.parse(contentType); - } else if (file) { - type = ContentType.APPLICATION_OCTET_STREAM; - } else { - type = ContentType.APPLICATION_FORM_URLENCODED.withCharset(UTF_8); - } - contentTypes.put(name, type); - - if (!hasFile && file) { - hasFile = true; - } - - return this; - } - - public MultipartBody field(String name, File file) { - return field(name, file, true, null); - } - - public MultipartBody field(String name, File file, String contentType) { - return field(name, file, true, contentType); - } - - public MultipartBody field(String name, InputStream stream, ContentType contentType, String fileName) { - return field(name, new InputStreamBody(stream, contentType, fileName), true, contentType.getMimeType()); - } - - public MultipartBody field(String name, InputStream stream, String fileName) { - return field(name, new InputStreamBody(stream, ContentType.APPLICATION_OCTET_STREAM, fileName), true, ContentType.APPLICATION_OCTET_STREAM.getMimeType()); - } - - public MultipartBody field(String name, byte[] bytes, ContentType contentType, String fileName) { - return field(name, new ByteArrayBody(bytes, contentType, fileName), true, contentType.getMimeType()); - } - - public MultipartBody field(String name, byte[] bytes, String fileName) { - return field(name, new ByteArrayBody(bytes, ContentType.APPLICATION_OCTET_STREAM, fileName), true, ContentType.APPLICATION_OCTET_STREAM.getMimeType()); - } - - public MultipartBody basicAuth(String username, String password) { - httpRequestObj.basicAuth(username, password); - return this; - } - - public MultipartBody mode(String mode) { - this.mode = HttpMultipartMode.valueOf(mode); - return this; - } - - public HttpEntity getEntity() { - if (hasFile) { - MultipartEntityBuilder builder = MultipartEntityBuilder.create(); - if (mode != null) { - builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); - } - for (String key : parameters.keySet()) { - List value = parameters.get(key); - ContentType contentType = contentTypes.get(key); - for (Object cur : value) { - if (cur instanceof File) { - File file = (File) cur; - builder.addPart(key, new FileBody(file, contentType, file.getName())); - } else if (cur instanceof InputStreamBody) { - builder.addPart(key, (ContentBody) cur); - } else if (cur instanceof ByteArrayBody) { - builder.addPart(key, (ContentBody) cur); - } else { - builder.addPart(key, new StringBody(cur.toString(), contentType)); - } - } - } - return builder.build(); - } else { - try { - return new UrlEncodedFormEntity(MapUtil.getList(parameters), UTF_8); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - } - } - -} diff --git a/src/main/java/com/mashape/unirest/request/body/RawBody.java b/src/main/java/com/mashape/unirest/request/body/RawBody.java deleted file mode 100644 index 02dd6d5fe..000000000 --- a/src/main/java/com/mashape/unirest/request/body/RawBody.java +++ /dev/null @@ -1,53 +0,0 @@ -/* -The MIT License - -Copyright (c) 2013 Mashape (http://mashape.com) - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.mashape.unirest.request.body; - -import com.mashape.unirest.request.BaseRequest; -import com.mashape.unirest.request.HttpRequest; -import org.apache.http.HttpEntity; -import org.apache.http.entity.ByteArrayEntity; - -public class RawBody extends BaseRequest implements Body { - - private byte[] body; - - public RawBody(HttpRequest httpRequest) { - super(httpRequest); - } - - public RawBody body(byte[] body) { - this.body = body; - return this; - } - - public Object getBody() { - return body; - } - - public HttpEntity getEntity() { - return new ByteArrayEntity(body); - } -} diff --git a/src/main/java/com/mashape/unirest/request/body/RequestBodyEntity.java b/src/main/java/com/mashape/unirest/request/body/RequestBodyEntity.java deleted file mode 100644 index 08377c95c..000000000 --- a/src/main/java/com/mashape/unirest/request/body/RequestBodyEntity.java +++ /dev/null @@ -1,61 +0,0 @@ -/* -The MIT License - -Copyright (c) 2013 Mashape (http://mashape.com) - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.mashape.unirest.request.body; - -import org.apache.http.HttpEntity; -import org.apache.http.entity.StringEntity; - -import com.mashape.unirest.http.JsonNode; -import com.mashape.unirest.request.BaseRequest; -import com.mashape.unirest.request.HttpRequest; - -public class RequestBodyEntity extends BaseRequest implements Body { - - private Object body; - - public RequestBodyEntity(HttpRequest httpRequest) { - super(httpRequest); - } - - public RequestBodyEntity body(String body) { - this.body = body; - return this; - } - - public RequestBodyEntity body(JsonNode body) { - this.body = body.toString(); - return this; - } - - public Object getBody() { - return body; - } - - public HttpEntity getEntity() { - return new StringEntity(body.toString(), UTF_8); - } - -} diff --git a/src/test/java/com/mashape/unirest/test/helper/GetResponse.java b/src/test/java/com/mashape/unirest/test/helper/GetResponse.java deleted file mode 100644 index 9005beee6..000000000 --- a/src/test/java/com/mashape/unirest/test/helper/GetResponse.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.mashape.unirest.test.helper; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -@JsonIgnoreProperties(ignoreUnknown = true) -public class GetResponse { - - @JsonProperty("url") - private String url; - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } -} diff --git a/src/test/java/com/mashape/unirest/test/helper/JacksonObjectMapper.java b/src/test/java/com/mashape/unirest/test/helper/JacksonObjectMapper.java deleted file mode 100644 index 0ff398f71..000000000 --- a/src/test/java/com/mashape/unirest/test/helper/JacksonObjectMapper.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.mashape.unirest.test.helper; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.mashape.unirest.http.ObjectMapper; - -import java.io.IOException; - -public class JacksonObjectMapper implements ObjectMapper { - - private com.fasterxml.jackson.databind.ObjectMapper jacksonObjectMapper = new com.fasterxml.jackson.databind.ObjectMapper(); - - public T readValue(String value, Class valueType) { - try { - return jacksonObjectMapper.readValue(value, valueType); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - public String writeValue(Object value) { - try { - return jacksonObjectMapper.writeValueAsString(value); - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - } -} diff --git a/src/test/java/com/mashape/unirest/test/http/UnirestTest.java b/src/test/java/com/mashape/unirest/test/http/UnirestTest.java deleted file mode 100644 index e7145a705..000000000 --- a/src/test/java/com/mashape/unirest/test/http/UnirestTest.java +++ /dev/null @@ -1,818 +0,0 @@ -/* -The MIT License - -Copyright (c) 2013 Mashape (http://mashape.com) - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.mashape.unirest.test.http; - -import com.mashape.unirest.http.*; -import com.mashape.unirest.http.async.Callback; -import com.mashape.unirest.http.exceptions.UnirestException; -import com.mashape.unirest.http.options.Options; -import com.mashape.unirest.request.GetRequest; -import com.mashape.unirest.request.HttpRequest; -import com.mashape.unirest.test.helper.GetResponse; -import com.mashape.unirest.test.helper.JacksonObjectMapper; - -import org.apache.commons.io.IOUtils; -import org.apache.http.entity.ContentType; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.nio.client.HttpAsyncClientBuilder; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import org.junit.Before; -import org.junit.Test; - -import java.io.*; -import java.net.InetAddress; -import java.net.URISyntaxException; -import java.net.UnknownHostException; -import java.util.Arrays; -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicInteger; - -import static org.junit.Assert.*; - -public class UnirestTest { - - private CountDownLatch lock; - private boolean status; - - @Before - public void setUp() { - lock = new CountDownLatch(1); - status = false; - } - - private String findAvailableIpAddress() throws UnknownHostException, IOException { - for (int i = 100; i <= 255; i++) { - String ip = "192.168.1." + i; - if (!InetAddress.getByName(ip).isReachable(1000)) { - return ip; - } - } - - throw new RuntimeException("Couldn't find an available IP address in the range of 192.168.0.100-255"); - } - - @Test - public void testRequests() throws JSONException, UnirestException { - HttpResponse jsonResponse = Unirest.post("http://httpbin.org/post").header("accept", "application/json").field("param1", "value1").field("param2", "bye").asJson(); - - assertTrue(jsonResponse.getHeaders().size() > 0); - assertTrue(jsonResponse.getBody().toString().length() > 0); - assertFalse(jsonResponse.getRawBody() == null); - assertEquals(200, jsonResponse.getStatus()); - - JsonNode json = jsonResponse.getBody(); - assertFalse(json.isArray()); - assertNotNull(json.getObject()); - assertNotNull(json.getArray()); - assertEquals(1, json.getArray().length()); - assertNotNull(json.getArray().get(0)); - } - - @Test - public void testGet() throws JSONException, UnirestException { - HttpResponse response = Unirest.get("http://httpbin.org/get?name=mark").asJson(); - assertEquals(response.getBody().getObject().getJSONObject("args").getString("name"), "mark"); - - response = Unirest.get("http://httpbin.org/get").queryString("name", "mark2").asJson(); - assertEquals(response.getBody().getObject().getJSONObject("args").getString("name"), "mark2"); - } - - @Test - public void testGetUTF8() throws UnirestException { - HttpResponse response = Unirest.get("http://httpbin.org/get").queryString("param3", "こんにづは").asJson(); - - assertEquals(response.getBody().getObject().getJSONObject("args").getString("param3"), "こんにづは"); - } - - @Test - public void testPostUTF8() throws UnirestException { - HttpResponse response = Unirest.post("http://httpbin.org/post").field("param3", "こんにづは").asJson(); - - assertEquals(response.getBody().getObject().getJSONObject("form").getString("param3"), "こんにづは"); - } - - @Test - public void testPostBinaryUTF8() throws UnirestException, URISyntaxException { - HttpResponse response = Unirest.post("http://httpbin.org/post").field("param3", "こんにづは").field("file", new File(getClass().getResource("/test").toURI())).asJson(); - - assertEquals("This is a test file", response.getBody().getObject().getJSONObject("files").getString("file")); - assertEquals("こんにづは", response.getBody().getObject().getJSONObject("form").getString("param3")); - } - - @Test - public void testPostRawBody() throws UnirestException, URISyntaxException, IOException { - String sourceString = "'\"@こんにづは-test-123-" + Math.random(); - byte[] sentBytes = sourceString.getBytes(); - - HttpResponse response = Unirest.post("http://httpbin.org/post").body(sentBytes).asJson(); - - assertEquals(sourceString, response.getBody().getObject().getString("data")); - } - - @Test - public void testCustomUserAgent() throws JSONException, UnirestException { - HttpResponse response = Unirest.get("http://httpbin.org/get?name=mark").header("user-agent", "hello-world").asJson(); - assertEquals("hello-world", response.getBody().getObject().getJSONObject("headers").getString("User-Agent")); - - GetRequest getRequest = Unirest.get("http"); - for (Object current : Arrays.asList(0, 1, 2)) { - getRequest.queryString("name", current); - } - - } - - @Test - public void testGetMultiple() throws JSONException, UnirestException { - for (int i = 1; i <= 20; i++) { - HttpResponse response = Unirest.get("http://httpbin.org/get?try=" + i).asJson(); - assertEquals(response.getBody().getObject().getJSONObject("args").getString("try"), ((Integer) i).toString()); - } - } - - @Test - public void testGetFields() throws JSONException, UnirestException { - HttpResponse response = Unirest.get("http://httpbin.org/get").queryString("name", "mark").queryString("nick", "thefosk").asJson(); - assertEquals(response.getBody().getObject().getJSONObject("args").getString("name"), "mark"); - assertEquals(response.getBody().getObject().getJSONObject("args").getString("nick"), "thefosk"); - } - - @Test - public void testGetFields2() throws JSONException, UnirestException { - HttpResponse response = Unirest.get("http://httpbin.org/get").queryString("email", "hello@hello.com").asJson(); - assertEquals("hello@hello.com", response.getBody().getObject().getJSONObject("args").getString("email")); - } - - @Test - public void testQueryStringEncoding() throws JSONException, UnirestException { - String testKey = "email2=someKey&email"; - String testValue = "hello@hello.com"; - HttpResponse response = Unirest.get("http://httpbin.org/get").queryString(testKey, testValue).asJson(); - assertEquals(testValue, response.getBody().getObject().getJSONObject("args").getString(testKey)); - } - - @Test - public void testDelete() throws JSONException, UnirestException { - HttpResponse response = Unirest.delete("http://httpbin.org/delete").asJson(); - assertEquals(200, response.getStatus()); - - response = Unirest.delete("http://httpbin.org/delete").field("name", "mark").asJson(); - assertEquals("mark", response.getBody().getObject().getJSONObject("form").getString("name")); - } - - @Test - public void testDeleteBody() throws JSONException, UnirestException { - String body = "{\"jsonString\":{\"members\":\"members1\"}}"; - HttpResponse response = Unirest.delete("http://httpbin.org/delete").body(body).asJson(); - assertEquals(200, response.getStatus()); - assertEquals(body, response.getBody().getObject().getString("data")); - } - - @Test - public void testBasicAuth() throws JSONException, UnirestException { - HttpResponse response = Unirest.get("http://httpbin.org/headers").basicAuth("user", "test").asJson(); - assertEquals("Basic dXNlcjp0ZXN0", response.getBody().getObject().getJSONObject("headers").getString("Authorization")); - } - - @Test - public void testAsync() throws JSONException, InterruptedException, ExecutionException { - Future> future = Unirest.post("http://httpbin.org/post").header("accept", "application/json").field("param1", "value1").field("param2", "bye").asJsonAsync(); - - assertNotNull(future); - HttpResponse jsonResponse = future.get(); - - assertTrue(jsonResponse.getHeaders().size() > 0); - assertTrue(jsonResponse.getBody().toString().length() > 0); - assertFalse(jsonResponse.getRawBody() == null); - assertEquals(200, jsonResponse.getStatus()); - - JsonNode json = jsonResponse.getBody(); - assertFalse(json.isArray()); - assertNotNull(json.getObject()); - assertNotNull(json.getArray()); - assertEquals(1, json.getArray().length()); - assertNotNull(json.getArray().get(0)); - } - - @Test - public void testAsyncCallback() throws JSONException, InterruptedException, ExecutionException { - Unirest.post("http://httpbin.org/post").header("accept", "application/json").field("param1", "value1").field("param2", "bye").asJsonAsync(new Callback() { - - public void failed(UnirestException e) { - fail(); - } - - public void completed(HttpResponse jsonResponse) { - assertTrue(jsonResponse.getHeaders().size() > 0); - assertTrue(jsonResponse.getBody().toString().length() > 0); - assertFalse(jsonResponse.getRawBody() == null); - assertEquals(200, jsonResponse.getStatus()); - - JsonNode json = jsonResponse.getBody(); - assertFalse(json.isArray()); - assertNotNull(json.getObject()); - assertNotNull(json.getArray()); - assertEquals(1, json.getArray().length()); - assertNotNull(json.getArray().get(0)); - - assertEquals("value1", json.getObject().getJSONObject("form").getString("param1")); - assertEquals("bye", json.getObject().getJSONObject("form").getString("param2")); - - status = true; - lock.countDown(); - } - - public void cancelled() { - fail(); - } - }); - - lock.await(10, TimeUnit.SECONDS); - assertTrue(status); - } - - @Test - public void testMultipart() throws JSONException, InterruptedException, ExecutionException, URISyntaxException, UnirestException { - HttpResponse jsonResponse = Unirest.post("http://httpbin.org/post").field("name", "Mark").field("file", new File(getClass().getResource("/test").toURI())).asJson(); - assertTrue(jsonResponse.getHeaders().size() > 0); - assertTrue(jsonResponse.getBody().toString().length() > 0); - assertFalse(jsonResponse.getRawBody() == null); - assertEquals(200, jsonResponse.getStatus()); - - JsonNode json = jsonResponse.getBody(); - assertFalse(json.isArray()); - assertNotNull(json.getObject()); - assertNotNull(json.getArray()); - assertEquals(1, json.getArray().length()); - assertNotNull(json.getArray().get(0)); - assertNotNull(json.getObject().getJSONObject("files")); - - assertEquals("This is a test file", json.getObject().getJSONObject("files").getString("file")); - assertEquals("Mark", json.getObject().getJSONObject("form").getString("name")); - } - - @Test - public void testMultipartContentType() throws JSONException, InterruptedException, ExecutionException, URISyntaxException, UnirestException { - HttpResponse jsonResponse = Unirest.post("http://httpbin.org/post").field("name", "Mark").field("file", new File(getClass().getResource("/image.jpg").toURI()), "image/jpeg").asJson(); - assertTrue(jsonResponse.getHeaders().size() > 0); - assertTrue(jsonResponse.getBody().toString().length() > 0); - assertFalse(jsonResponse.getRawBody() == null); - assertEquals(200, jsonResponse.getStatus()); - - JsonNode json = jsonResponse.getBody(); - assertFalse(json.isArray()); - assertNotNull(json.getObject()); - assertNotNull(json.getArray()); - assertEquals(1, json.getArray().length()); - assertNotNull(json.getArray().get(0)); - assertNotNull(json.getObject().getJSONObject("files")); - - assertTrue(json.getObject().getJSONObject("files").getString("file").contains("data:image/jpeg")); - assertEquals("Mark", json.getObject().getJSONObject("form").getString("name")); - } - - @Test - public void testMultipartInputStreamContentType() throws JSONException, InterruptedException, ExecutionException, URISyntaxException, UnirestException, FileNotFoundException { - HttpResponse jsonResponse = Unirest.post("http://httpbin.org/post").field("name", "Mark").field("file", new FileInputStream(new File(getClass().getResource("/image.jpg").toURI())), ContentType.APPLICATION_OCTET_STREAM, "image.jpg").asJson(); - assertTrue(jsonResponse.getHeaders().size() > 0); - assertTrue(jsonResponse.getBody().toString().length() > 0); - assertFalse(jsonResponse.getRawBody() == null); - assertEquals(200, jsonResponse.getStatus()); - - JsonNode json = jsonResponse.getBody(); - assertFalse(json.isArray()); - assertNotNull(json.getObject()); - assertNotNull(json.getArray()); - assertEquals(1, json.getArray().length()); - assertNotNull(json.getArray().get(0)); - assertNotNull(json.getObject().getJSONObject("files")); - - assertTrue(json.getObject().getJSONObject("files").getString("file").contains("data:application/octet-stream")); - assertEquals("Mark", json.getObject().getJSONObject("form").getString("name")); - } - - @Test - public void testMultipartInputStreamContentTypeAsync() throws JSONException, InterruptedException, ExecutionException, URISyntaxException, UnirestException, FileNotFoundException { - Unirest.post("http://httpbin.org/post").field("name", "Mark").field("file", new FileInputStream(new File(getClass().getResource("/test").toURI())), ContentType.APPLICATION_OCTET_STREAM, "test").asJsonAsync(new Callback() { - - public void failed(UnirestException e) { - fail(); - } - - public void completed(HttpResponse response) { - assertTrue(response.getHeaders().size() > 0); - assertTrue(response.getBody().toString().length() > 0); - assertFalse(response.getRawBody() == null); - assertEquals(200, response.getStatus()); - - JsonNode json = response.getBody(); - assertFalse(json.isArray()); - assertNotNull(json.getObject()); - assertNotNull(json.getArray()); - assertEquals(1, json.getArray().length()); - assertNotNull(json.getArray().get(0)); - - assertEquals("This is a test file", json.getObject().getJSONObject("files").getString("file")); - assertEquals("Mark", json.getObject().getJSONObject("form").getString("name")); - - status = true; - lock.countDown(); - } - - public void cancelled() { - fail(); - } - - }); - - lock.await(10, TimeUnit.SECONDS); - assertTrue(status); - } - - @Test - public void testMultipartByteContentType() throws JSONException, InterruptedException, ExecutionException, URISyntaxException, UnirestException, IOException { - final InputStream stream = new FileInputStream(new File(getClass().getResource("/image.jpg").toURI())); - final byte[] bytes = new byte[stream.available()]; - stream.read(bytes); - stream.close(); - HttpResponse jsonResponse = Unirest.post("http://httpbin.org/post").field("name", "Mark").field("file", bytes, "image.jpg").asJson(); - assertTrue(jsonResponse.getHeaders().size() > 0); - assertTrue(jsonResponse.getBody().toString().length() > 0); - assertFalse(jsonResponse.getRawBody() == null); - assertEquals(200, jsonResponse.getStatus()); - - JsonNode json = jsonResponse.getBody(); - assertFalse(json.isArray()); - assertNotNull(json.getObject()); - assertNotNull(json.getArray()); - assertEquals(1, json.getArray().length()); - assertNotNull(json.getArray().get(0)); - assertNotNull(json.getObject().getJSONObject("files")); - - assertTrue(json.getObject().getJSONObject("files").getString("file").contains("data:application/octet-stream")); - assertEquals("Mark", json.getObject().getJSONObject("form").getString("name")); - } - - @Test - public void testMultipartByteContentTypeAsync() throws JSONException, InterruptedException, ExecutionException, URISyntaxException, UnirestException, IOException { - final InputStream stream = new FileInputStream(new File(getClass().getResource("/test").toURI())); - final byte[] bytes = new byte[stream.available()]; - stream.read(bytes); - stream.close(); - Unirest.post("http://httpbin.org/post").field("name", "Mark").field("file", bytes, "test").asJsonAsync(new Callback() { - - public void failed(UnirestException e) { - fail(); - } - - public void completed(HttpResponse response) { - assertTrue(response.getHeaders().size() > 0); - assertTrue(response.getBody().toString().length() > 0); - assertFalse(response.getRawBody() == null); - assertEquals(200, response.getStatus()); - - JsonNode json = response.getBody(); - assertFalse(json.isArray()); - assertNotNull(json.getObject()); - assertNotNull(json.getArray()); - assertEquals(1, json.getArray().length()); - assertNotNull(json.getArray().get(0)); - - assertEquals("This is a test file", json.getObject().getJSONObject("files").getString("file")); - assertEquals("Mark", json.getObject().getJSONObject("form").getString("name")); - - status = true; - lock.countDown(); - } - - public void cancelled() { - fail(); - } - - }); - - lock.await(10, TimeUnit.SECONDS); - assertTrue(status); - } - - @Test - public void testMultipartAsync() throws JSONException, InterruptedException, ExecutionException, URISyntaxException, UnirestException { - Unirest.post("http://httpbin.org/post").field("name", "Mark").field("file", new File(getClass().getResource("/test").toURI())).asJsonAsync(new Callback() { - - public void failed(UnirestException e) { - fail(); - } - - public void completed(HttpResponse response) { - assertTrue(response.getHeaders().size() > 0); - assertTrue(response.getBody().toString().length() > 0); - assertFalse(response.getRawBody() == null); - assertEquals(200, response.getStatus()); - - JsonNode json = response.getBody(); - assertFalse(json.isArray()); - assertNotNull(json.getObject()); - assertNotNull(json.getArray()); - assertEquals(1, json.getArray().length()); - assertNotNull(json.getArray().get(0)); - - assertEquals("This is a test file", json.getObject().getJSONObject("files").getString("file")); - assertEquals("Mark", json.getObject().getJSONObject("form").getString("name")); - - status = true; - lock.countDown(); - } - - public void cancelled() { - fail(); - } - - }); - - lock.await(10, TimeUnit.SECONDS); - assertTrue(status); - } - - @Test - public void testGzip() throws UnirestException, JSONException { - HttpResponse jsonResponse = Unirest.get("http://httpbin.org/gzip").asJson(); - assertTrue(jsonResponse.getHeaders().size() > 0); - assertTrue(jsonResponse.getBody().toString().length() > 0); - assertFalse(jsonResponse.getRawBody() == null); - assertEquals(200, jsonResponse.getStatus()); - - JsonNode json = jsonResponse.getBody(); - assertFalse(json.isArray()); - assertTrue(json.getObject().getBoolean("gzipped")); - } - - @Test - public void testGzipAsync() throws UnirestException, JSONException, InterruptedException, ExecutionException { - HttpResponse jsonResponse = Unirest.get("http://httpbin.org/gzip").asJsonAsync().get(); - assertTrue(jsonResponse.getHeaders().size() > 0); - assertTrue(jsonResponse.getBody().toString().length() > 0); - assertFalse(jsonResponse.getRawBody() == null); - assertEquals(200, jsonResponse.getStatus()); - - JsonNode json = jsonResponse.getBody(); - assertFalse(json.isArray()); - assertTrue(json.getObject().getBoolean("gzipped")); - } - - @Test - public void testDefaultHeaders() throws UnirestException, JSONException { - Unirest.setDefaultHeader("X-Custom-Header", "hello"); - Unirest.setDefaultHeader("user-agent", "foobar"); - - HttpResponse jsonResponse = Unirest.get("http://httpbin.org/headers").asJson(); - assertTrue(jsonResponse.getHeaders().size() > 0); - assertTrue(jsonResponse.getBody().toString().length() > 0); - assertFalse(jsonResponse.getRawBody() == null); - assertEquals(200, jsonResponse.getStatus()); - - JsonNode json = jsonResponse.getBody(); - assertFalse(json.isArray()); - assertTrue(jsonResponse.getBody().getObject().getJSONObject("headers").has("X-Custom-Header")); - assertEquals("hello", json.getObject().getJSONObject("headers").getString("X-Custom-Header")); - assertTrue(jsonResponse.getBody().getObject().getJSONObject("headers").has("User-Agent")); - assertEquals("foobar", json.getObject().getJSONObject("headers").getString("User-Agent")); - - jsonResponse = Unirest.get("http://httpbin.org/headers").asJson(); - assertTrue(jsonResponse.getBody().getObject().getJSONObject("headers").has("X-Custom-Header")); - assertEquals("hello", jsonResponse.getBody().getObject().getJSONObject("headers").getString("X-Custom-Header")); - - Unirest.clearDefaultHeaders(); - - jsonResponse = Unirest.get("http://httpbin.org/headers").asJson(); - assertFalse(jsonResponse.getBody().getObject().getJSONObject("headers").has("X-Custom-Header")); - } - - @Test - public void testSetTimeouts() throws UnknownHostException, IOException { - String address = "http://" + findAvailableIpAddress() + "/"; - long start = System.currentTimeMillis(); - try { - Unirest.get("http://" + address + "/").asString(); - } catch (Exception e) { - if (System.currentTimeMillis() - start > Options.CONNECTION_TIMEOUT + 100) { // Add 100ms for code execution - fail(); - } - } - Unirest.setTimeouts(2000, 10000); - start = System.currentTimeMillis(); - try { - Unirest.get("http://" + address + "/").asString(); - } catch (Exception e) { - if (System.currentTimeMillis() - start > 2100) { // Add 100ms for code execution - fail(); - } - } - } - - @Test - public void testPathParameters() throws UnirestException { - HttpResponse jsonResponse = Unirest.get("http://httpbin.org/{method}").routeParam("method", "get").queryString("name", "Mark").asJson(); - - assertEquals(200, jsonResponse.getStatus()); - assertEquals(jsonResponse.getBody().getObject().getJSONObject("args").getString("name"), "Mark"); - } - - @Test - public void testQueryAndBodyParameters() throws UnirestException { - HttpResponse jsonResponse = Unirest.post("http://httpbin.org/{method}").routeParam("method", "post").queryString("name", "Mark").field("wot", "wat").asJson(); - - assertEquals(200, jsonResponse.getStatus()); - assertEquals(jsonResponse.getBody().getObject().getJSONObject("args").getString("name"), "Mark"); - assertEquals(jsonResponse.getBody().getObject().getJSONObject("form").getString("wot"), "wat"); - } - - @Test - public void testPathParameters2() throws UnirestException { - HttpResponse jsonResponse = Unirest.patch("http://httpbin.org/{method}").routeParam("method", "patch").field("name", "Mark").asJson(); - - assertEquals(200, jsonResponse.getStatus()); - assertEquals("OK", jsonResponse.getStatusText()); - assertEquals(jsonResponse.getBody().getObject().getJSONObject("form").getString("name"), "Mark"); - } - - @Test - public void testMissingPathParameter() throws UnirestException { - try { - Unirest.get("http://httpbin.org/{method}").routeParam("method222", "get").queryString("name", "Mark").asJson(); - fail(); - } catch (RuntimeException e) { - // OK - } - } - - @Test - public void parallelTest() throws InterruptedException { - Unirest.setConcurrency(10, 5); - - long start = System.currentTimeMillis(); - makeParallelRequests(); - long smallerConcurrencyTime = (System.currentTimeMillis() - start); - - Unirest.setConcurrency(200, 20); - start = System.currentTimeMillis(); - makeParallelRequests(); - long higherConcurrencyTime = (System.currentTimeMillis() - start); - - assertTrue(higherConcurrencyTime < smallerConcurrencyTime); - } - - private void makeParallelRequests() throws InterruptedException { - ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(10); - final AtomicInteger counter = new AtomicInteger(0); - for (int i = 0; i < 200; i++) { - newFixedThreadPool.execute(new Runnable() { - public void run() { - try { - Unirest.get("http://httpbin.org/get").queryString("index", counter.incrementAndGet()).asJson(); - } catch (UnirestException e) { - throw new RuntimeException(e); - } - } - }); - } - - newFixedThreadPool.shutdown(); - newFixedThreadPool.awaitTermination(10, TimeUnit.MINUTES); - } - - @Test - public void testAsyncCustomContentType() throws InterruptedException { - Unirest.post("http://httpbin.org/post").header("accept", "application/json").header("Content-Type", "application/json").body("{\"hello\":\"world\"}").asJsonAsync(new Callback() { - - public void failed(UnirestException e) { - fail(); - } - - public void completed(HttpResponse jsonResponse) { - JsonNode json = jsonResponse.getBody(); - assertEquals("{\"hello\":\"world\"}", json.getObject().getString("data")); - assertEquals("application/json", json.getObject().getJSONObject("headers").getString("Content-Type")); - - status = true; - lock.countDown(); - } - - public void cancelled() { - fail(); - } - }); - - lock.await(10, TimeUnit.SECONDS); - assertTrue(status); - } - - @Test - public void testAsyncCustomContentTypeAndFormParams() throws InterruptedException { - Unirest.post("http://httpbin.org/post").header("accept", "application/json").header("Content-Type", "application/x-www-form-urlencoded").field("name", "Mark").field("hello", "world").asJsonAsync(new Callback() { - - public void failed(UnirestException e) { - fail(); - } - - public void completed(HttpResponse jsonResponse) { - JsonNode json = jsonResponse.getBody(); - assertEquals("Mark", json.getObject().getJSONObject("form").getString("name")); - assertEquals("world", json.getObject().getJSONObject("form").getString("hello")); - - assertEquals("application/x-www-form-urlencoded", json.getObject().getJSONObject("headers").getString("Content-Type")); - - status = true; - lock.countDown(); - } - - public void cancelled() { - fail(); - } - }); - - lock.await(10, TimeUnit.SECONDS); - assertTrue(status); - } - - @Test - public void testGetQuerystringArray() throws JSONException, UnirestException { - HttpResponse response = Unirest.get("http://httpbin.org/get").queryString("name", "Mark").queryString("name", "Tom").asJson(); - - JSONArray names = response.getBody().getObject().getJSONObject("args").getJSONArray("name"); - assertEquals(2, names.length()); - - assertEquals("Mark", names.getString(0)); - assertEquals("Tom", names.getString(1)); - } - - @Test - public void testPostMultipleFiles() throws JSONException, UnirestException, URISyntaxException { - HttpResponse response = Unirest.post("http://httpbin.org/post").field("param3", "wot").field("file1", new File(getClass().getResource("/test").toURI())).field("file2", new File(getClass().getResource("/test").toURI())).asJson(); - - JSONObject names = response.getBody().getObject().getJSONObject("files"); - assertEquals(2, names.length()); - - assertEquals("This is a test file", names.getString("file1")); - assertEquals("This is a test file", names.getString("file2")); - - assertEquals("wot", response.getBody().getObject().getJSONObject("form").getString("param3")); - } - - @Test - public void testGetArray() throws JSONException, UnirestException { - HttpResponse response = Unirest.get("http://httpbin.org/get").queryString("name", Arrays.asList("Mark", "Tom")).asJson(); - - JSONArray names = response.getBody().getObject().getJSONObject("args").getJSONArray("name"); - assertEquals(2, names.length()); - - assertEquals("Mark", names.getString(0)); - assertEquals("Tom", names.getString(1)); - } - - @Test - public void testPostArray() throws JSONException, UnirestException { - HttpResponse response = Unirest.post("http://httpbin.org/post").field("name", "Mark").field("name", "Tom").asJson(); - - JSONArray names = response.getBody().getObject().getJSONObject("form").getJSONArray("name"); - assertEquals(2, names.length()); - - assertEquals("Mark", names.getString(0)); - assertEquals("Tom", names.getString(1)); - } - - @Test - public void testPostCollection() throws JSONException, UnirestException { - HttpResponse response = Unirest.post("http://httpbin.org/post").field("name", Arrays.asList("Mark", "Tom")).asJson(); - - JSONArray names = response.getBody().getObject().getJSONObject("form").getJSONArray("name"); - assertEquals(2, names.length()); - - assertEquals("Mark", names.getString(0)); - assertEquals("Tom", names.getString(1)); - } - - @Test - public void testCaseInsensitiveHeaders() throws UnirestException { - GetRequest request = Unirest.get("http://httpbin.org/headers").header("Name", "Marco"); - assertEquals(1, request.getHeaders().size()); - assertEquals("Marco", request.getHeaders().get("name").get(0)); - assertEquals("Marco", request.getHeaders().get("NAme").get(0)); - assertEquals("Marco", request.getHeaders().get("Name").get(0)); - JSONObject headers = request.asJson().getBody().getObject().getJSONObject("headers"); - assertEquals("Marco", headers.getString("Name")); - - request = Unirest.get("http://httpbin.org/headers").header("Name", "Marco").header("Name", "John"); - assertEquals(1, request.getHeaders().size()); - assertEquals("Marco", request.getHeaders().get("name").get(0)); - assertEquals("John", request.getHeaders().get("name").get(1)); - assertEquals("Marco", request.getHeaders().get("NAme").get(0)); - assertEquals("John", request.getHeaders().get("NAme").get(1)); - assertEquals("Marco", request.getHeaders().get("Name").get(0)); - assertEquals("John", request.getHeaders().get("Name").get(1)); - - headers = request.asJson().getBody().getObject().getJSONObject("headers"); - assertEquals("Marco,John", headers.get("Name")); - } - - @Test - public void setTimeoutsAndCustomClient() { - try { - Unirest.setTimeouts(1000, 2000); - } catch (Exception e) { - fail(); - } - - try { - Unirest.setAsyncHttpClient(HttpAsyncClientBuilder.create().build()); - } catch (Exception e) { - fail(); - } - - try { - Unirest.setAsyncHttpClient(HttpAsyncClientBuilder.create().build()); - Unirest.setTimeouts(1000, 2000); - fail(); - } catch (Exception e) { - // Ok - } - - try { - Unirest.setHttpClient(HttpClientBuilder.create().build()); - Unirest.setTimeouts(1000, 2000); - fail(); - } catch (Exception e) { - // Ok - } - } - - @Test - public void testObjectMapperRead() throws UnirestException, IOException { - Unirest.setObjectMapper(new JacksonObjectMapper()); - - GetResponse getResponseMock = new GetResponse(); - getResponseMock.setUrl("http://httpbin.org/get"); - - HttpResponse getResponse = Unirest.get(getResponseMock.getUrl()).asObject(GetResponse.class); - - assertEquals(200, getResponse.getStatus()); - assertEquals(getResponse.getBody().getUrl(), getResponseMock.getUrl()); - } - - @Test - public void testObjectMapperWrite() throws UnirestException, IOException { - Unirest.setObjectMapper(new JacksonObjectMapper()); - - GetResponse postResponseMock = new GetResponse(); - postResponseMock.setUrl("http://httpbin.org/post"); - - HttpResponse postResponse = Unirest.post(postResponseMock.getUrl()).header("accept", "application/json").header("Content-Type", "application/json").body(postResponseMock).asJson(); - - assertEquals(200, postResponse.getStatus()); - assertEquals(postResponse.getBody().getObject().getString("data"), "{\"url\":\"http://httpbin.org/post\"}"); - } - - @Test - public void testPostProvidesSortedParams() throws IOException { - // Verify that fields are encoded into the body in sorted order. - HttpRequest httpRequest = Unirest.post("test").field("z", "Z").field("y", "Y").field("x", "X").getHttpRequest(); - - InputStream content = httpRequest.getBody().getEntity().getContent(); - String body = IOUtils.toString(content, "UTF-8"); - assertEquals("x=X&y=Y&z=Z", body); - } - - @Test - public void testHeaderNamesCaseSensitive() { - // Verify that header names are the same as server (case sensitive) - final Headers headers = new Headers(); - headers.put("Content-Type", Arrays.asList("application/json")); - - assertEquals("Only header \"Content-Type\" should exist", null, headers.getFirst("cOnTeNt-TyPe")); - assertEquals("Only header \"Content-Type\" should exist", null, headers.getFirst("content-type")); - assertEquals("Only header \"Content-Type\" should exist", "application/json", headers.getFirst("Content-Type")); - } -} diff --git a/unirest-bdd-tests/pom.xml b/unirest-bdd-tests/pom.xml new file mode 100644 index 000000000..6df6b5967 --- /dev/null +++ b/unirest-bdd-tests/pom.xml @@ -0,0 +1,75 @@ + + + + unirest-java-parent + com.konghq + 4.10.3-SNAPSHOT + + 4.0.0 + + unirest-bdd-tests + unirest-bdd-tests + + + 23 + 23 + ${project.parent.basedir} + + + + + com.konghq + unirest-java-core + ${project.version} + test + + + com.konghq + unirest-modules-gson + ${project.version} + test + + + io.javalin + javalin + 7.2.2 + test + + + tools.jackson.datatype + jackson-datatype-guava + ${jackson.version} + test + + + tools.jackson.core + jackson-databind + ${jackson.version} + test + + + org.slf4j + slf4j-simple + 2.0.17 + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + true + 128m + 512m + 23 + 23 + UTF-8 + + + + + diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/AsBytesTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/AsBytesTest.java new file mode 100644 index 000000000..bbbf9dc78 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/AsBytesTest.java @@ -0,0 +1,102 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class AsBytesTest extends BddTest { + JacksonObjectMapper om = new JacksonObjectMapper(); + + @Test + void getGetResultAsBytes() { + var content = Unirest.get(MockServer.GET) + .asBytes() + .getBody(); + + var cap = om.readValue(content, RequestCapture.class); + + cap.assertStatus(200); + } + + @Test + void getGetBinaryResultAsBytes() { + var content = Unirest.get(MockServer.BINARYFILE) + .asBytes() + .getBody(); + + assertEquals(46246, content.length); + } + + @Test + void getGetResultAsBytesAsync() throws Exception { + var content = Unirest.get(MockServer.GET) + .asBytesAsync() + .get() + .getBody(); + + var cap = om.readValue(content, RequestCapture.class); + + cap.assertStatus(200); + } + + @Test + void getGetResultAsBytesAsyncCallback() throws Exception { + Unirest.get(MockServer.GET) + .queryString("fruit", "apple") + .asBytesAsync(r -> { + RequestCapture cap = om.readValue(r.getBody(), RequestCapture.class); + cap.assertParam("fruit", "apple"); + asyncSuccess(); + }) + .get() + .getBody(); + + assertAsync(); + } + + @Test // https://github.com/Kong/unirest-java/issues/424 + void mappingErrorsFromAsBytes() { + MockServer.setStringResponse("howdy"); + var r = Unirest.get(MockServer.ERROR_RESPONSE) + .asBytes() + .mapError(String.class); + + assertEquals("howdy", r); + } + + @Test // https://github.com/Kong/unirest-java/issues/424 + void mappingErrorsFromAsBytesMapped() { + MockServer.setJsonAsResponse(new Foo("howdy")); + var r = Unirest.get(MockServer.ERROR_RESPONSE) + .asBytes() + .mapError(Foo.class); + + assertEquals("howdy", r.bar); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/AsEmptyTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/AsEmptyTest.java new file mode 100644 index 000000000..4b966a5ab --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/AsEmptyTest.java @@ -0,0 +1,82 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.Empty; +import kong.unirest.core.HttpResponse; +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + + +class AsEmptyTest extends BddTest { + + @Test + void asEmptyParams() { + Unirest.get(MockServer.ERROR_RESPONSE) + .asEmpty() + .ifFailure(response -> { + assertEquals(400, response.getStatus()); + }) + .ifSuccess(s -> fail("fail")); + } + + @Test + void canDoAEmptyRequestThatDoesNotParseBodyAtAll() { + MockServer.addResponseHeader("Content-Type", "json"); + var res = Unirest.get(MockServer.GET).asEmpty(); + + assertNull(res.getBody()); + assertEquals(200, res.getStatus()); + assertEquals("json;charset=utf-8", res.getHeaders().getFirst("Content-Type")); + } + + @Test + void canDoEmptyAsync() throws Exception { + MockServer.addResponseHeader("Content-Type", "json"); + var res = Unirest.get(MockServer.GET).asEmptyAsync().get(); + + assertNull(res.getBody()); + assertEquals(200, res.getStatus()); + assertEquals("json;charset=utf-8", res.getHeaders().getFirst("Content-Type")); + } + + @Test + void canDoEmptyAsyncWithCallback() { + MockServer.addResponseHeader("Content-Type", "json;charset=utf-8"); + + Unirest.get(MockServer.GET) + .asEmptyAsync(res -> { + assertNull(res.getBody()); + assertEquals(200, res.getStatus()); + assertEquals("json;charset=utf-8", res.getHeaders().getFirst("Content-Type")); + asyncSuccess(); + }); + + assertAsync(); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/AsFileTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/AsFileTest.java new file mode 100644 index 000000000..7699520d5 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/AsFileTest.java @@ -0,0 +1,168 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.HttpResponse; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import kong.unirest.core.Unirest; + +import java.io.File; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class AsFileTest extends BddTest { + + private final Path test = Paths.get("results.json"); + private final JacksonObjectMapper om = new JacksonObjectMapper(); + + @Override @AfterEach + public void tearDown() { + try { + Files.delete(test); + } catch (Exception ignored) { } + } + + @Test + void canSaveContentsIntoFile() { + var result = Unirest.get(MockServer.GET) + .queryString("talking","heads") + .queryString("param3", "こんにづは") + .asFile(test.toString()) + .getBody(); + + om.readValue(result, RequestCapture.class) + .assertParam("talking", "heads") + .assertParam("param3", "こんにづは") + .assertStatus(200); + + assertEquals(test.toFile().getPath(), result.getPath()); + } + + @Test + void canSaveContentsIntoFileAsync() throws Exception { + var result = Unirest.get(MockServer.GET) + .queryString("talking","heads") + .queryString("param3", "こんにづは") + .asFileAsync(test.toString()) + .get() + .getBody(); + + om.readValue(result, RequestCapture.class) + .assertParam("talking", "heads") + .assertParam("param3", "こんにづは") + .assertStatus(200); + + assertEquals(test.toFile().getPath(), result.getPath()); + } + + @Test + void canSaveContentsIntoFileAsyncWithCallback() throws Exception { + Unirest.get(MockServer.GET) + .queryString("talking","heads") + .queryString("param3", "こんにづは") + .asFileAsync(test.toString(), r -> { + om.readValue(r.getBody(), RequestCapture.class) + .assertParam("talking", "heads") + .assertParam("param3", "こんにづは") + .assertStatus(200); + assertEquals(test.toFile().getPath(), r.getBody().getPath()); + asyncSuccess(); + }); + + assertAsync(); + } + + @Test + void canDownloadABinaryFile() throws Exception { + var f1 = TestUtil.rezFile("/spidey.jpg"); + + var f2 = Unirest.get(MockServer.BINARYFILE) + .asFile(test.toString()) + .getBody(); + + assertTrue(com.google.common.io.Files.equal(f1, f2)); + } + + @Test + void byDefaultFailWhenAttemptingToOverride() { + Unirest.get(MockServer.BINARYFILE) + .asFile(test.toString()); + + var f2 = Unirest.get(MockServer.BINARYFILE) + .asFile(test.toString()); + + assertTrue(f2.getParsingError().isPresent()); + assertThat(f2.getParsingError().get().getCause().getCause()) + .isInstanceOf(FileAlreadyExistsException.class); + } + + @Test + void canOverrideExistingFiles() { + var f1 = Unirest.get(MockServer.BINARYFILE) + .asFile(test.toString()) + .getBody(); + + var f2 = Unirest.get(MockServer.BINARYFILE) + .asFile(test.toString(), StandardCopyOption.REPLACE_EXISTING) + .getBody(); + + assertEquals(f1, f2); + } + + @Test + void canOverrideExistingFiles_Async() throws Exception { + var f1 = Unirest.get(MockServer.BINARYFILE) + .asFileAsync(test.toString()) + .get() + .getBody(); + + var f2 = Unirest.get(MockServer.BINARYFILE) + .asFileAsync(test.toString(), StandardCopyOption.REPLACE_EXISTING) + .get() + .getBody(); + + assertEquals(f1, f2); + } + + @Test + void dontReadFilesWhenInError() { + var f1 = Unirest.get(MockServer.BINARYFILE) + .downloadMonitor((field, fileName, bytesWritten, totalBytes) -> { + throw new RuntimeException("hey hey hey"); + }) + .asFile(test.toString()); + + assertEquals("", f1.getParsingError().get().getOriginalBody()); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/AsGenericTypeTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/AsGenericTypeTest.java new file mode 100644 index 000000000..86528685c --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/AsGenericTypeTest.java @@ -0,0 +1,134 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.Unirest; +import kong.unirest.core.GenericType; +import kong.unirest.modules.gson.GsonObjectMapper; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ExecutionException; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class AsGenericTypeTest extends BddTest { + + private final List foos = Arrays.asList( + new Foo("foo"), + new Foo("bar"), + new Foo("baz") + ); + + @Test + void canGetAListOfObjects() { + MockServer.setJsonAsResponse(foos); + + var foos = Unirest.get(MockServer.GET) + .asObject(new GenericType>(){}) + .getBody(); + + assertTheFoos(foos); + } + + @Test + void canGetAListOfObjectsAsync() throws ExecutionException, InterruptedException { + MockServer.setJsonAsResponse(foos); + + var foos = Unirest.get(MockServer.GET) + .asObjectAsync(new GenericType>(){}) + .get() + .getBody(); + + assertTheFoos(foos); + } + + @Test + void canGetAListOfObjectsAsyncWithCallback() { + MockServer.setJsonAsResponse(foos); + + Unirest.get(MockServer.GET) + .asObjectAsync(new GenericType>(){}, + r -> { + List f = r.getBody(); + assertTheFoos(f); + asyncSuccess(); + }); + + assertAsync(); + } + + @Test + void soManyLayersOfGenerics() { + MockServer.setJsonAsResponse(new WeirdType<>(foos, "hey")); + + var foos = Unirest.get(MockServer.GET) + .asObject(new GenericType>>(){}) + .getBody(); + + var someTees = foos.getSomeTees(); + assertTheFoos(someTees); + } + + @Test + void itAlsoWorksWithGson() { + Unirest.config().setObjectMapper(new GsonObjectMapper()); + + MockServer.setJsonAsResponse(new WeirdType<>(foos, "hey")); + + var foos = Unirest.get(MockServer.GET) + .asObject(new GenericType>>(){}) + .getBody(); + + var someTees = foos.getSomeTees(); + assertTheFoos(someTees); + } + + private void assertTheFoos(List someTees) { + assertEquals(3, someTees.size()); + assertEquals("foo", someTees.get(0).bar); + assertEquals("bar", someTees.get(1).bar); + assertEquals("baz", someTees.get(2).bar); + } + + public static class WeirdType { + + private T someTees; + private String words; + + public WeirdType(){} + + public WeirdType(T someTees, String words){ + this.someTees = someTees; + this.words = words; + } + + public T getSomeTees() { + return someTees; + } + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/AsJsonTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/AsJsonTest.java new file mode 100644 index 000000000..922d69606 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/AsJsonTest.java @@ -0,0 +1,145 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.*; +import kong.unirest.core.json.JSONObject; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +import static org.junit.jupiter.api.Assertions.*; + +class AsJsonTest extends BddTest { + + @Test + void whenNoBodyIsReturned() { + var i = Unirest.get(MockServer.NOBODY).asJson(); + + assertEquals(HttpStatus.OK, i.getStatus()); + assertEquals("{}", i.getBody().toString()); + } + + @Test + void toStringAObject() { + MockServer.setStringResponse(new JSONObject().put("f", 1).put("a", Arrays.asList(2, 3, 4)).toString()); + var i = Unirest.get(MockServer.GET).asJson(); + + assertEquals("{\"f\":1,\"a\":[2,3,4]}", i.getBody().toString()); + } + + @Test + void toPrettyStringAObject() { + MockServer.setStringResponse(new JSONObject().put("f", 1).put("a", Arrays.asList(2, 3, 4)).toString()); + var i = Unirest.get(MockServer.GET).asJson(); + + assertEquals("{\n" + + " \"f\": 1,\n" + + " \"a\": [\n" + + " 2,\n" + + " 3,\n" + + " 4\n" + + " ]\n" + + "}", i.getBody().toPrettyString()); + } + + @Test + void canGetBinaryResponse() { + var i = Unirest.get(MockServer.GET) + .queryString("foo", "bar") + .asJson(); + + assertJson(i); + } + + @Test + void canGetBinaryResponseAsync() throws Exception { + var r = Unirest.get(MockServer.GET) + .queryString("foo", "bar") + .asJsonAsync(); + + assertJson(r.get()); + } + + @Test + void canGetBinaryResponseAsyncWithCallback() { + Unirest.get(MockServer.GET) + .queryString("foo", "bar") + .asJsonAsync(r -> { + assertJson(r); + asyncSuccess(); + }); + + assertAsync(); + } + + @Test + void failureToReturnValidJsonWillResultInAnEmptyNode() { + var response = Unirest.get(MockServer.INVALID_REQUEST).asJson(); + + assertEquals(HttpStatus.BAD_REQUEST, response.getStatus()); + assertNull(response.getBody()); + assertTrue(response.getParsingError().isPresent()); + + var ex = response.getParsingError().get(); + assertEquals("You did something bad", ex.getOriginalBody()); + assertEquals("kong.unirest.core.json.JSONException: Invalid JSON", + response.getParsingError().get().getMessage()); + + } + + @Test + void failureToReturnValidJsonWillResultInAnEmptyNodeAsync() { + Unirest.get(MockServer.INVALID_REQUEST) + .asJsonAsync(new MockCallback<>(this, response -> { + assertEquals(HttpStatus.BAD_REQUEST, response.getStatus()); + assertNull(response.getBody()); + assertTrue(response.getParsingError().isPresent()); + assertEquals("kong.unirest.core.json.JSONException: Invalid JSON", + response.getParsingError().get().getMessage()); + + })); + + assertAsync(); + } + + @Test + void doNotEscapeHTML() { + MockServer.setStringResponse("{\"test\":\"it's a && b || c + 1!?\"}"); + + var test = Unirest.get(MockServer.GET) + .asJson() + .getBody() + .getObject(); + + assertEquals("{\"test\":\"it's a && b || c + 1!?\"}", test.toString()); + } + + private void assertJson(HttpResponse i) { + assertEquals("bar", i.getBody().getObject().getJSONObject("params").getJSONArray("foo").get(0)); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/AsObjectTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/AsObjectTest.java new file mode 100644 index 000000000..872a1d0ea --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/AsObjectTest.java @@ -0,0 +1,260 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import tools.jackson.databind.DeserializationFeature; +import com.google.gson.Gson; +import kong.unirest.core.*; +import kong.unirest.modules.gson.GsonObjectMapper; +import org.junit.jupiter.api.Test; +import tools.jackson.databind.json.JsonMapper; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +class AsObjectTest extends BddTest { + + @Test + void basicJsonObjectMapperIsTheDefault() { + Unirest.config().reset(true); + assertThat(Unirest.config().getObjectMapper()) + .isInstanceOf(GsonObjectMapper.class); + } + + @Test + void basicJsonObjectMapperIsGoodEnough() { + Unirest.config().reset(true); + MockServer.setJsonAsResponse(new Foo("bar")); + + Foo f = Unirest.get(MockServer.GET) + .asObject(Foo.class) + .getBody(); + + assertEquals("bar", f.bar); + } + + @Test + void whenNoBodyIsReturned() { + var i = Unirest.get(MockServer.NOBODY).asObject(RequestCapture.class); + + assertEquals(200, i.getStatus()); + assertNull(i.getBody()); + } + + @Test + void canGetObjectResponse() { + Unirest.get(MockServer.GET) + .queryString("foo", "bar") + .asObject(RequestCapture.class) + .getBody() + .assertParam("foo", "bar"); + } + + @Test + void canGetObjectResponseAsync() throws Exception { + Unirest.get(MockServer.GET) + .queryString("foo", "bar") + .asObjectAsync(RequestCapture.class) + .get() + .getBody() + .assertParam("foo", "bar"); + } + + @Test + void canGetObjectResponseAsyncWithCallback() { + Unirest.get(MockServer.GET) + .queryString("foo", "bar") + .asObjectAsync(RequestCapture.class, r -> { + RequestCapture cap = r.getBody(); + cap.assertParam("foo", "bar"); + asyncSuccess(); + }); + + assertAsync(); + } + + @Test + void canPassAnObjectMapperAsPartOfARequest(){ + Unirest.config().setObjectMapper(null); + + var mapper = new TestingMapper(); + Unirest.post(MockServer.POST) + .queryString("foo", "bar") + .withObjectMapper(mapper) + .body(new Foo("Hello World")) + .asObject(RequestCapture.class) + .ifFailure(e -> { + UnirestParsingException ex = e.getParsingError().get(); + ex.printStackTrace(); + throw new RuntimeException(ex.getMessage()); + }) + .getBody() + .assertParam("foo", "bar") + .assertBody("{\"bar\":\"Hello World\"}"); + + assertTrue(mapper.readWasCalled); + assertTrue(mapper.writeWasCalled); + } + + @Test + void canOverrideBody() { + Unirest.post(MockServer.POST) + .body(new Foo("Apple")) + .body(new Foo("Orange")) + .asObject(RequestCapture.class) + .getBody() + .assertBody("{\"bar\":\"Orange\"}"); + } + + @Test + void setCharSetAfterBody() { + Unirest.post(MockServer.POST) + .body(new Foo("Orange")) + .charset(StandardCharsets.US_ASCII) + .asObject(RequestCapture.class) + .getBody() + .assertContentType("text/plain", "charset", "US-ASCII"); + } + + @Test + void setNoCharSetAfterBody() { + Unirest.post(MockServer.POST) + .body(new Foo("Orange")) + .noCharset() + .asObject(RequestCapture.class) + .getBody() + .assertContentType("text/plain"); + } + + @Test + void ifTheObjectMapperFailsReturnEmptyAndAddToParsingError() { + var request = Unirest.get(MockServer.INVALID_REQUEST) + .asObject(RequestCapture.class); + + assertNull(request.getBody()); + assertTrue(request.getParsingError().isPresent()); + assertThat(request.getParsingError().get().getMessage()) + .startsWith("kong.unirest.core.UnirestException: tools.jackson.core.exc.StreamReadException: " + + "Unrecognized token 'You': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')"); + assertEquals("You did something bad", request.getParsingError().get().getOriginalBody()); + + } + + @Test + void ifTheObjectMapperFailsReturnEmptyAndAddToParsingErrorObGenericTypes() { + var request = Unirest.get(MockServer.INVALID_REQUEST) + .asObject(new GenericType() {}); + + assertNull(request.getBody()); + assertTrue(request.getParsingError().isPresent()); + assertThat(request.getParsingError().get().getMessage()) + .startsWith("kong.unirest.core.UnirestException: tools.jackson.core.exc.StreamReadException: " + + "Unrecognized token 'You': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')"); + assertEquals("You did something bad", request.getParsingError().get().getOriginalBody()); + } + + @Test + void unirestExceptionsAreAlsoParseExceptions() { + var request = Unirest.get(MockServer.INVALID_REQUEST) + .asObject(new GenericType() {}); + + assertNull(request.getBody()); + assertTrue(request.getParsingError().isPresent()); + assertThat(request.getParsingError().get().getMessage()) + .startsWith("kong.unirest.core.UnirestException: tools.jackson.core.exc.StreamReadException: " + + "Unrecognized token 'You': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')"); + assertEquals("You did something bad", request.getParsingError().get().getOriginalBody()); + } + + @Test + void canSetObjectMapperToFailOnUnknown() { + var jack = JsonMapper.builderWithJackson2Defaults().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true).build(); + + Unirest.config().setObjectMapper(new JacksonObjectMapper(jack)); + + MockServer.setStringResponse("{\"foo\": [1,2,3] }"); + + var error = Unirest.get(MockServer.GET) + .asObject(RequestCapture.class) + .mapError(Error.class); + + assertEquals(Arrays.asList(1,2,3), error.foo); + } + + @Test + void parsingExceptions() { + MockServer.setStringResponse("hi im not json"); + + var ex = Unirest.get(MockServer.ERROR_RESPONSE) + .asObject(SomeTestClass.class) + .getParsingError() + .get(); + + assertParsingError(ex); + + var ex2 = Unirest.get(MockServer.ERROR_RESPONSE) + .asObject(new GenericType() {}) + .getParsingError() + .get(); + + assertParsingError(ex2); + + } + + private static void assertParsingError(UnirestParsingException e) { + assertThat(e) + .isInstanceOf(UnirestParsingException.class) + .hasMessage("kong.unirest.core.UnirestException: tools.jackson.core.exc.StreamReadException: Unrecognized token 'hi': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')\n" + + " at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); byte offset: #UNKNOWN]"); + } + + static class SomeTestClass {} + + public static class Error { + public List foo; + } + + public static class TestingMapper implements ObjectMapper { + public boolean readWasCalled; + public boolean writeWasCalled; + + @Override + public T readValue(String value, Class valueType) { + this.readWasCalled = true; + return new JacksonObjectMapper().readValue(value, valueType); + } + + @Override + public String writeValue(Object value) { + writeWasCalled = true; + return new Gson().toJson(value); + } + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/AsPagedTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/AsPagedTest.java new file mode 100644 index 000000000..aad469c35 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/AsPagedTest.java @@ -0,0 +1,144 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.HttpRequest; +import kong.unirest.core.HttpResponse; +import kong.unirest.core.PagedList; +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; + +class AsPagedTest extends BddTest { + + @Test + void canFollowPaging() { + MockServer.expectedPages(10); + + PagedList result = Unirest.get(MockServer.PAGED) + .header("x-header", "h-value") + .asPaged( + r -> r.asObject(RequestCapture.class), + r -> r.getHeaders().getFirst("nextPage") + ); + + assertThat(result) + .hasSize(10) + .allMatch(r -> { + r.getBody().assertHeader("x-header", "h-value"); + return true; + }); + } + + @Test + void pagedRequestsAreUnique() { + MockServer.expectedPages(5); + + var result = Unirest.get(MockServer.PAGED) + .header("x-header", "h-value") + .asPaged( + r -> r.asObject(RequestCapture.class), + r -> r.getHeaders().getFirst("nextPage") + ); + + var sums = result.stream() + .map(HttpResponse::getRequestSummary) + .collect(Collectors.toList()); + + assertThat(sums) + .hasSize(5) + .extracting(e -> e.getUrl()) + .containsExactly( + "http://localhost:4567/paged", + "http://localhost:4567/paged?page=2", + "http://localhost:4567/paged?page=3", + "http://localhost:4567/paged?page=4", + "http://localhost:4567/paged?page=5" + ); + + } + + @Test + void canFollowPagingForPost() { + MockServer.expectedPages(10); + + PagedList result = Unirest.post(MockServer.PAGED) + .body("Hi Mom") + .asPaged( + r -> r.asObject(RequestCapture.class), + r -> r.getHeaders().getFirst("nextPage") + ); + + assertThat(result) + .hasSize(10) + .allMatch(r -> { + r.getBody().assertBody("Hi Mom"); + return true; + }); + } + + @Test + void canCapturePagesAsStrings() { + MockServer.expectedPages(10); + + PagedList result = Unirest.get(MockServer.PAGED) + .asPaged( + r -> r.asString(), + r -> r.getHeaders().getFirst("nextPage") + ); + + assertThat(result).hasSize(10); + } + + @Test + void willReturnOnePageIfthereWasNoPaging() { + PagedList result = Unirest.get(MockServer.PAGED) + .asPaged( + r -> r.asObject(RequestCapture.class), + r -> null + ); + + assertThat(result).hasSize(1); + } + + @Test + void asPagedWithRedirects() { + Unirest.config().followRedirects(false); + + var responses = Unirest.get(MockServer.REDIRECT) + .asPaged(HttpRequest::asString, + response -> List.of(301, 302).contains(response.getStatus()) + ? "http://localhost:4567/" + response.getHeaders().getFirst("Location") + : null); + + + assertThat(responses).hasSize(2); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/AsStringTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/AsStringTest.java new file mode 100644 index 000000000..51ca098ae --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/AsStringTest.java @@ -0,0 +1,154 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + + +import io.javalin.http.Header; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import kong.unirest.core.HttpResponse; +import kong.unirest.core.Unirest; + +import java.util.concurrent.CompletableFuture; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class AsStringTest extends BddTest { + + @Test + void simpleExample() { + MockServer.setStringResponse("Hi Mom"); + var body = Unirest.get(MockServer.GET).asString().getBody(); + assertEquals("Hi Mom", body); + } + + @Test + void whenNoBodyIsReturned() { + var i = Unirest.get(MockServer.NOBODY).asString(); + + assertEquals(200, i.getStatus()); + assertEquals("", i.getBody()); + } + + @Test + void canParseGzippedStringResponse() { + var i = Unirest.get(MockServer.GZIP) + .queryString("foo", "bar") + .header(Header.ACCEPT_ENCODING, "gzip") + .asString(); + + assertEquals(200, i.getStatus()); + + var cap = TestUtil.readValue(i.getBody(), RequestCapture.class); + cap.assertParam("foo", "bar"); + } + + @Test + void canParseGzippedResponseAsync() throws Exception { + var i = Unirest.get(MockServer.GZIP) + .queryString("foo", "bar") + .asStringAsync().get(); + + assertEquals(200, i.getStatus()); + + var cap = TestUtil.readValue(i.getBody(), RequestCapture.class); + cap.assertParam("foo", "bar"); + } + + @Test + void canGetBinaryResponse() { + HttpResponse i = Unirest.get(MockServer.GET) + .queryString("foo", "bar") + .asString(); + + var cap = TestUtil.readValue(i.getBody(), RequestCapture.class); + cap.assertParam("foo", "bar"); + } + + @Test + void canGetBinaryResponseAsync() throws Exception { + var r = Unirest.get(MockServer.GET) + .queryString("foo", "bar") + .asStringAsync(); + + var cap = TestUtil.readValue(r.get().getBody(), RequestCapture.class); + cap.assertParam("foo", "bar"); + } + + @Test + void canGetBinaryResponseAsyncWithCallback() { + Unirest.get(MockServer.GET) + .queryString("foo", "bar") + .asStringAsync(r -> { + RequestCapture cap = TestUtil.readValue(r.getBody(), RequestCapture.class); + cap.assertParam("foo", "bar"); + asyncSuccess(); + }); + + assertAsync(); + } + + @Test + void unicodeResponse() { + MockServer.setStringResponse("Ä›ĆĄÄĆ™ĆŸĂœĂĄĂ­Ă©"); + + assertEquals("Ä›ĆĄÄĆ™ĆŸĂœĂĄĂ­Ă©", Unirest.get(MockServer.GET).asString().getBody()); + } + + @Test + void unicodeResponseAsync() throws Exception { + MockServer.setStringResponse("Ä›ĆĄÄĆ™ĆŸĂœĂĄĂ­Ă©"); + + Unirest.get(MockServer.GET) + .asStringAsync(r -> { + assertEquals("Ä›ĆĄÄĆ™ĆŸĂœĂĄĂ­Ă©", r.getBody()); + asyncSuccess(); + }); + + assertAsync(); + } + + @Test @Disabled + void canSetExpectedCharsetOfResponse() { + var response = Unirest.get(MockServer.WINDOWS_LATIN_1_FILE) + .responseEncoding("windows-1250") + .asString(); + + assertEquals(200, response.getStatus()); + assertEquals("ĆĄĆŸĂœĂĄĂ­Ă©", response.getBody()); + } + + @Test @Disabled + void canSetDefaultCharsetOfResponse() { + Unirest.config().setDefaultResponseEncoding("windows-1250"); + + var response = Unirest.get(MockServer.WINDOWS_LATIN_1_FILE) + .asString(); + + assertEquals(200, response.getStatus()); + assertEquals("ĆĄĆŸĂœĂĄĂ­Ă©", response.getBody()); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/AuthenticatorTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/AuthenticatorTest.java new file mode 100644 index 000000000..885bd95ca --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/AuthenticatorTest.java @@ -0,0 +1,94 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.HttpResponse; +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; + +import java.net.Authenticator; +import java.net.InetAddress; +import java.net.PasswordAuthentication; +import java.net.URL; + +import static org.assertj.core.api.Assertions.assertThat; + +public class AuthenticatorTest extends BddTest { + @Test + void getBlockedWithoutAuth(){ + assertThat(Unirest.get(MockServer.BASICAUTH).asEmpty()) + .returns(401, HttpResponse::getStatus) + .returns("Basic realm=\"User Visible Realm\", charset=\"UTF-8\"", r -> r.getHeaders().getFirst("WWW-Authenticate")); + } + + @Test + void setAuthenticator(){ + Unirest.config().authenticator(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication("Fred", "Flintstone".toCharArray()); + } + }); + + Unirest.get(MockServer.BASICAUTH) + .asObject(RequestCapture.class) + .getBody() + .assertBasicAuth("Fred", "Flintstone"); + + MockServer.assertRequestCount(2); + } + + @Test + void setAuthenticatorPerHostAndPort(){ + Unirest.config().authenticator(new Authenticator() { + @Override + public PasswordAuthentication requestPasswordAuthenticationInstance(String host, InetAddress addr, int port, + String protocol, String prompt, + String scheme, URL url, + RequestorType reqType) { + if(host.contains("localhost")){ + return new PasswordAuthentication("Barney", "Rubble".toCharArray()); + } else { + return new PasswordAuthentication("Fred", "Flintstone".toCharArray()); + } + } + }); + + Unirest.get(MockServer.BASICAUTH) + .queryString("barney", "true") + .asObject(RequestCapture.class) + .getBody() + .assertBasicAuth("Barney", "Rubble"); + + // The Authenticator will cache for different ports and hosts. + Unirest.get(MockServer.BASICAUTH.replace("localhost", "127.0.0.1")) + .asObject(RequestCapture.class) + .getBody() + .assertBasicAuth("Fred", "Flintstone"); + + + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/BaseUrlTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/BaseUrlTest.java new file mode 100644 index 000000000..443030c11 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/BaseUrlTest.java @@ -0,0 +1,64 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; + +class BaseUrlTest extends BddTest { + + @Test + void canConfigureADefaultBaseUrl() { + Unirest.config().defaultBaseUrl(MockServer.HOST); + + Unirest.get("/get") + .asObject(RequestCapture.class) + .getBody() + .assertUrl(MockServer.GET); + } + + @Test + void willNotUseTheDefaultIfOverridden() { + Unirest.config().defaultBaseUrl("http://somewhere"); + + Unirest.get(MockServer.GET) + .asObject(RequestCapture.class) + .getBody() + .assertUrl(MockServer.GET); + } + + @Test + void defaultUrlsCanHavePlaceholders() { + Unirest.config().defaultBaseUrl(MockServer.PASSED_PATH_PARAM); + + Unirest.get("/{another}") + .routeParam("params", "George") + .routeParam("another", "Ringo") + .asObject(RequestCapture.class) + .getBody() + .assertUrl("http://localhost:4567/get/George/passed/Ringo"); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/BddTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/BddTest.java new file mode 100644 index 000000000..512cebb93 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/BddTest.java @@ -0,0 +1,92 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.HttpResponse; +import kong.unirest.core.JsonNode; +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.*; + +public class BddTest { + private static final JacksonObjectMapper objectMapper = new JacksonObjectMapper(); + private CountDownLatch lock; + private boolean status; + private String fail; + + @BeforeEach + public void setUp() { + Unirest.shutDown(true); + //TestUtil.debugApache(); + MockServer.reset(); + Unirest.config().setObjectMapper(objectMapper); + lock = new CountDownLatch(1); + status = false; + TestSSEConsumer.reset(); + } + + @AfterEach + public void tearDown() { + Unirest.shutDown(true); + TestUtil.reset(); + } + + public void assertAsync() { + try { + lock.await(5, TimeUnit.SECONDS); + assertTrue(status, "Expected a async call but it never responded"); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + public void assertFailed(String message) throws InterruptedException { + lock.await(5, TimeUnit.SECONDS); + assertFalse(status, "Should have failed"); + assertEquals(message, fail); + } + + public void asyncSuccess() { + status = true; + lock.countDown(); + } + + public void asyncFail(String message) { + status = false; + lock.countDown(); + fail = message; + } + + public static RequestCapture parse(HttpResponse response) { + assertEquals(200, response.getStatus()); + return objectMapper.readValue(response.getBody().toString(), RequestCapture.class); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/BodyLogSummaryTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/BodyLogSummaryTest.java new file mode 100644 index 000000000..4e811bc77 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/BodyLogSummaryTest.java @@ -0,0 +1,237 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.MultiPartBuilder; +import kong.unirest.core.Unirest; +import kong.unirest.core.json.JSONObject; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import static BehaviorTests.TestUtil.rezFile; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +class BodyLogSummaryTest extends BddTest { + + @Test + void forSimpleGets() { + var log = Unirest.get("http://somewhere/{magic}") + .routeParam("magic", "beans") + .queryString("fruit", "apple") + .header("Accept", "image/raw") + .toSummary() + .asString(); + + assertEquals("GET http://somewhere/beans?fruit=apple\n" + + "Accept: image/raw\n" + + "===================================", log); + } + + @Test + void forSimpleBodies() { + var log = Unirest.post("http://somewhere/{magic}") + .routeParam("magic", "beans") + .queryString("fruit", "apple") + .header("Accept", "image/raw") + .body("this is the body") + .toSummary() + .asString(); + + assertEquals("POST http://somewhere/beans?fruit=apple\n" + + "Accept: image/raw\n" + + "===================================\n" + + "this is the body", log); + } + + @Test + void forJsonBodies() { + var log = Unirest.post("http://somewhere/{magic}") + .routeParam("magic", "beans") + .queryString("fruit", "apple") + .header("Accept", "image/raw") + .body(new JSONObject().put("muppet","Gonzo")) + .toSummary() + .asString(); + + assertEquals("POST http://somewhere/beans?fruit=apple\n" + + "Accept: image/raw\n" + + "===================================\n" + + "{\"muppet\":\"Gonzo\"}", log); + } + + @Test + void forObjectBodies() { + var log = Unirest.post("http://somewhere/{magic}") + .routeParam("magic", "beans") + .queryString("fruit", "apple") + .header("Accept", "image/raw") + .body(new Foo("zip")) + .toSummary() + .asString(); + + assertEquals("POST http://somewhere/beans?fruit=apple\n" + + "Accept: image/raw\n" + + "===================================\n" + + "{\"bar\":\"zip\"}", log); + } + + @Test + void simpleFormBody() { + var log = Unirest.post("http://somewhere/{magic}") + .routeParam("magic", "beans") + .queryString("fruit", "apple") + .header("Accept", "image/raw") + .field("band", "Talking Heads") + .field("album", "77") + .toSummary() + .asString(); + + assertEquals("POST http://somewhere/beans?fruit=apple\n" + + "Accept: image/raw\n" + + "===================================\n" + + "band=Talking+Heads&album=77", log); + } + + @Test + void simpleFormBodySorted() { + var log = Unirest.post("http://somewhere/{magic}") + .routeParam("magic", "beans") + .queryString("fruit", "apple") + .header("Accept", "image/raw") + .field("band", "Talking Heads") + .field("album", "77") + .sortFields() + .toSummary() + .asString(); + + assertEquals("POST http://somewhere/beans?fruit=apple\n" + + "Accept: image/raw\n" + + "===================================\n" + + "album=77&band=Talking+Heads", log); + } + + @Test + void multiPart() { + var boundary = "ABC-123-BOUNDARY"; + var body = Unirest.post(MockServer.ECHO_RAW) + .header("Accept", "image/raw") + .field("band", "Talking Heads") + .field("album", "77") + .field("file", rezFile("/test.txt")) + .boundary(boundary) + .toSummary() + .asString(); + + assertThat(body).isEqualTo( + "POST http://localhost:4567/raw\n" + + "Accept: image/raw\n" + + "Content-Type: multipart/form-data; boundary=ABC-123-BOUNDARY;charset=UTF-8\"\n" + + "===================================\n" + + "--ABC-123-BOUNDARY\n" + + "Content-Disposition: form-data; name:\"band\"\n" + + "Content-Type: application/x-www-form-urlencoded; charset=UTF-8\n" + + "Talking Heads\n" + + "\n" + + "--ABC-123-BOUNDARY\n" + + "Content-Disposition: form-data; name:\"album\"\n" + + "Content-Type: application/x-www-form-urlencoded; charset=UTF-8\n" + + "77\n" + + "\n" + + "--ABC-123-BOUNDARY\n" + + "Content-Disposition: form-data; name=\"file\"; filename=\"test.txt\"\n" + + "Content-Type: application/octet-stream\n" + + "\n" + ); + + } + + @Test + void multiPartWithExtraHeaders() { + var boundary = "ABC-123-BOUNDARY"; + var body = Unirest.post(MockServer.ECHO_RAW) + .field(MultiPartBuilder.named("file").value(rezFile("/test.txt")).header("foo", "bar")) + .field(MultiPartBuilder.named("metadata").value("cheese").header("baz", "qux")) + .boundary(boundary) + .toSummary() + .asString(); + + assertThat(body).isEqualTo( + "POST http://localhost:4567/raw\n" + + "Content-Type: multipart/form-data; boundary=ABC-123-BOUNDARY;charset=UTF-8\"\n" + + "===================================\n" + + "--ABC-123-BOUNDARY\n" + + "Content-Disposition: form-data; name=\"file\"; filename=\"test.txt\"\n" + + "Content-Type: application/octet-stream\n" + + "foo: bar\n" + + "\n" + + "\n" + + "--ABC-123-BOUNDARY\n" + + "Content-Disposition: form-data; name:\"metadata\"\n" + + "Content-Type: application/x-www-form-urlencoded; charset=UTF-8\n" + + "baz: qux\n" + + "cheese\n" + ); + + } + + @Test + void multiPartSorted() { + var boundary = "ABC-123-BOUNDARY"; + var body = Unirest.post(MockServer.ECHO_RAW) + .header("Accept", "image/raw") + .field("band", "Talking Heads") + .field("album", "77") + .field("file", rezFile("/test.txt")) + .sortFields() + .boundary(boundary) + .toSummary() + .asString(); + + assertThat(body).isEqualTo( + "POST http://localhost:4567/raw\n" + + "Accept: image/raw\n" + + "Content-Type: multipart/form-data; boundary=ABC-123-BOUNDARY;charset=UTF-8\"\n" + + "===================================\n" + + "--ABC-123-BOUNDARY\n" + + "Content-Disposition: form-data; name:\"album\"\n" + + "Content-Type: application/x-www-form-urlencoded; charset=UTF-8\n" + + "77\n" + + "\n" + + "--ABC-123-BOUNDARY\n" + + "Content-Disposition: form-data; name:\"band\"\n" + + "Content-Type: application/x-www-form-urlencoded; charset=UTF-8\n" + + "Talking Heads\n" + + "\n" + + "--ABC-123-BOUNDARY\n" + + "Content-Disposition: form-data; name=\"file\"; filename=\"test.txt\"\n" + + "Content-Type: application/octet-stream\n" + + "\n" + ); + + } + +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/CachingAlternativeTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/CachingAlternativeTest.java new file mode 100644 index 000000000..6f8627c7a --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/CachingAlternativeTest.java @@ -0,0 +1,125 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import com.google.common.cache.CacheBuilder; +import kong.unirest.core.Cache; +import kong.unirest.core.HttpResponse; +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; + +import java.time.Instant; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.function.Supplier; + +import static org.junit.jupiter.api.Assertions.*; + +class CachingAlternativeTest extends BddTest { + + @Test + void canSupplyCustomCache() { + var cache = new GuavaCache(); + Unirest.config().cacheResponses(Cache.builder().backingCache(cache)); + + assertSame( + Unirest.get(MockServer.GET).asString(), + Unirest.get(MockServer.GET).asString() + ); + + assertNotSame( + Unirest.get(MockServer.GET).asString(), + Unirest.get(MockServer.GET).asObject(RequestCapture.class) + ); + + assertEquals(2, MockServer.timesCalled); + + cache.invalidate(); + + assertSame( + Unirest.get(MockServer.GET).asString(), + Unirest.get(MockServer.GET).asString() + ); + + assertEquals(3, MockServer.timesCalled); + } + + @Test + void supplyCustomCacheKeyFunction() { + Unirest.config().cacheResponses(Cache.builder().withKeyGen((r,a,z) -> new CustomKey())); + + assertSame( + Unirest.get(MockServer.GET).asString(), + Unirest.get(MockServer.POST).asString() + ); + + assertEquals(1, MockServer.timesCalled); + } + + public static class GuavaCache implements Cache { + com.google.common.cache.Cache regular = CacheBuilder.newBuilder().build(); + com.google.common.cache.Cache async = CacheBuilder.newBuilder().build(); + @Override + public HttpResponse get(Key key, Supplier> fetcher) { + try { + return regular.get(key, fetcher::get); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } + } + + @Override + public CompletableFuture getAsync(Key key, Supplier>> fetcher) { + try { + return async.get(key, fetcher::get); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } + } + + public void invalidate() { + regular.invalidateAll(); + async.invalidateAll(); + } + } + + class CustomKey implements Cache.Key { + @Override + public int hashCode() { + return 1; + } + + @Override + public boolean equals(Object obj) { + return true; + } + + @Override + public Instant getTime() { + return Instant.now(); + } + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/CachingTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/CachingTest.java new file mode 100644 index 000000000..43462978f --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/CachingTest.java @@ -0,0 +1,185 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.Clock; +import kong.unirest.core.HttpResponse; +import kong.unirest.core.JsonNode; +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.concurrent.TimeUnit; +import java.util.stream.IntStream; + +import static com.google.common.collect.ImmutableMap.of; +import static kong.unirest.core.Cache.builder; +import static org.junit.jupiter.api.Assertions.*; + +class CachingTest extends BddTest { + + @Test + void doesNotCacheByDefault() { + Unirest.get(MockServer.GET).asEmpty(); + Unirest.get(MockServer.GET).asEmpty(); + + assertEquals(2, MockServer.timesCalled); + } + + @Test + void canCacheResponsesForEqualRequests() { + Unirest.config().cacheResponses(true); + + var r1 = Unirest.get(MockServer.GET).asObject(RequestCapture.class).getBody().requestId; + var r2 = Unirest.get(MockServer.GET).asObject(RequestCapture.class).getBody().requestId; + + assertEquals(1, MockServer.timesCalled); + assertEquals(r1, r2); + } + + @Test + void queryAlsoCaches() { + Unirest.config().cacheResponses(true); + + var r1 = Unirest.get(MockServer.QUERY).asObject(RequestCapture.class).getBody().requestId; + var r2 = Unirest.get(MockServer.QUERY).asObject(RequestCapture.class).getBody().requestId; + + assertEquals(1, MockServer.timesCalled); + assertEquals(r1, r2); + } + + @Test + void canCacheResponsesForEqualRequests_async() throws Exception { + Unirest.config().cacheResponses(true); + + var r1 = Unirest.get(MockServer.GET).asObjectAsync(RequestCapture.class).get().getBody().requestId; + var r2 = Unirest.get(MockServer.GET).asObjectAsync(RequestCapture.class).get().getBody().requestId; + + assertEquals(1, MockServer.timesCalled); + assertEquals(r1, r2); + } + + @Test + void theSameRequestForDifferentTypesAreDifferent() { + Unirest.config().cacheResponses(true); + + assertSame( + Unirest.get(MockServer.GET).asString(), + Unirest.get(MockServer.GET).asString() + ); + + assertNotSame( + Unirest.get(MockServer.GET).asString(), + Unirest.get(MockServer.GET).asObject(RequestCapture.class) + ); + + assertEquals(2, MockServer.timesCalled); + } + + @Test + void allTheTypesAreUniqueSnowflakes() { + Unirest.config().cacheResponses(true); + + assertNull(Unirest.get(MockServer.GET).asEmpty().getBody()); + assertNull(Unirest.get(MockServer.GET).asEmpty().getBody()); + assertNotNull(Unirest.get(MockServer.GET).asObject(RequestCapture.class).getBody()); + assertNotNull(Unirest.get(MockServer.GET).asObject(RequestCapture.class).getBody()); + assertNotNull(Unirest.get(MockServer.GET).asString().getBody()); + assertNotNull(Unirest.get(MockServer.GET).asString().getBody()); + assertNotNull(Unirest.get(MockServer.GET).asJson().getBody()); + assertNotNull(Unirest.get(MockServer.GET).asJson().getBody()); + assertNotNull(Unirest.get(MockServer.GET).asBytes().getBody()); + assertNotNull(Unirest.get(MockServer.GET).asBytes().getBody()); + + assertEquals(5, MockServer.timesCalled); + } + + @Test + void canSetAMaxDepthOfCache() { + Unirest.config().cacheResponses(builder().depth(5)); + + IntStream.range(0,10).forEach(i -> { + Unirest.get(MockServer.GET).queryString("no",i).asEmpty(); + Unirest.get(MockServer.GET).queryString("no",i).asEmpty(); + }); + + IntStream.range(5,10).forEach(i -> { + Unirest.get(MockServer.GET).queryString("no",i).asEmpty(); + Unirest.get(MockServer.GET).queryString("no",i).asEmpty(); + }); + + assertEquals(10, MockServer.timesCalled); + + Unirest.get(MockServer.GET).queryString("no",0).asEmpty(); + + assertEquals(11, MockServer.timesCalled); + } + + @Test + void canSetATTLOfACacheEntry() { + Instant now = Instant.now(); + Clock.freeze(now); + + Unirest.config().cacheResponses(builder().maxAge(5, TimeUnit.MINUTES)); + + Unirest.get(MockServer.GET).asEmpty(); + Unirest.get(MockServer.GET).asEmpty(); + + assertEquals(1, MockServer.timesCalled); + + Clock.freeze(now.plus(10, ChronoUnit.MINUTES)); + + Unirest.get(MockServer.GET).asEmpty(); + + assertEquals(2, MockServer.timesCalled); + } + + @Test + void doesNotCacheByDefault_json() { + HttpResponse response1 = Unirest.get(MockServer.GET).queryString(of("a",1)).asJson(); + HttpResponse response2 = Unirest.get(MockServer.GET).queryString(of("a",1)).asJson(); + + assertNotEquals( + response1.getBody().getObject().getString("requestId"), + response2.getBody().getObject().getString("requestId") + ); + assertEquals(2, MockServer.timesCalled); + } + + @Test + void willCacheIfEnabled_json() { + Unirest.config().cacheResponses(true); + var response1 = Unirest.get(MockServer.GET).queryString(of("a",1)).asJson(); + var response2 = Unirest.get(MockServer.GET).queryString(of("a",1)).asJson(); + + assertEquals( + response1.getBody().getObject().getString("requestId"), + response2.getBody().getObject().getString("requestId") + ); + assertEquals(1, MockServer.timesCalled); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/CallbackFutureTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/CallbackFutureTest.java new file mode 100644 index 000000000..65646e6dc --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/CallbackFutureTest.java @@ -0,0 +1,127 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.*; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import java.util.concurrent.CompletableFuture; + +import static BehaviorTests.MockCallback.json; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class CallbackFutureTest extends BddTest { + + @Test @Timeout(5) + void onSuccess() throws Exception { + Unirest.get(MockServer.GET) + .queryString("Snazzy", "Shoes") + .asJsonAsync() + .thenAccept(r -> { + parse(r).assertParam("Snazzy", "Shoes"); + asyncSuccess(); + }).get(); + + assertAsync(); + } + + @Test @Timeout(5) + void onSuccessSupplyCallback() throws Exception { + Unirest.get(MockServer.GET) + .queryString("Snazzy", "Shoes") + .asJsonAsync(new NoopCallback<>()) + .thenAccept(r -> { + parse(r).assertParam("Snazzy", "Shoes"); + asyncSuccess(); + }).get(); + + assertAsync(); + } + + @Test @Timeout(5) @Disabled + void onFailure() throws Exception { + Unirest.get("http://localhost:0000") + .asJsonAsync(json(this)) + .isCompletedExceptionally(); + + assertFailed("java.net.ConnectException: Connection refused"); + } + + boolean wasCalled = false; + + @Test @Disabled + void callFailureFunction() throws Exception { + wasCalled = false; + try { + Unirest.get("http://localhost:0000") + .asJsonAsync(new Callback() { + @Override + public void completed(HttpResponse response) { + throw new UnirestException("Failure!"); + } + + @Override + public void failed(UnirestException e) { + wasCalled = true; + } + + @Override + public void cancelled() { + + } + }).get(); + }catch (Exception e){} + + assertTrue(wasCalled); + } + + @Test @Disabled + void cancelFunction() throws Exception { + wasCalled = false; + CompletableFuture> future = Unirest.get("http://localhost:0000") + .asJsonAsync(new Callback() { + @Override + public void completed(HttpResponse response) { + throw new UnirestException("Failure!"); + } + + @Override + public void failed(UnirestException e) { + + } + + @Override + public void cancelled() { + wasCalled = true; + } + }); + + future.cancel(true); + assertTrue(wasCalled); + } +} \ No newline at end of file diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/CertificateTests.java b/unirest-bdd-tests/src/test/java/BehaviorTests/CertificateTests.java new file mode 100644 index 000000000..b95454551 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/CertificateTests.java @@ -0,0 +1,363 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.GetRequest; + +import kong.unirest.core.HttpResponse; +import kong.unirest.core.Unirest; +import kong.unirest.core.UnirestException; +import kong.unirest.core.java.SSLContextBuilder; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import javax.net.ssl.*; + +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.security.KeyStore; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; + +@Disabled // don't normally run these because they depend on badssl.com +class CertificateTests extends BddTest { + + public static final char[] PASSWORD = "badssl.com".toCharArray(); + + @Test + void canDoClientCertificates() throws Exception { + Unirest.config().clientCertificateStore(readStore(), "badssl.com"); + + Unirest.get("https://client.badssl.com/") + .asString() + .ifFailure(r -> fail(r.getStatus() + " request failed " + r.getBody())) + .ifSuccess(r -> System.out.println(" woot ")); + } + + + @Test + void canLoadKeyStoreByPath() { + Unirest.config().clientCertificateStore("src/test/resources/certs/badssl.com-client.p12", "badssl.com"); + + Unirest.get("https://client.badssl.com/") + .asString() + .ifFailure(r -> fail(r.getStatus() + " request failed " + r.getBody())) + .ifSuccess(r -> System.out.println(" woot ")); + ; + } + + @Test + void loadWithSSLContext() throws Exception { + SSLContext sslContext = SSLContextBuilder.create() + .loadKeyMaterial(readStore(), "badssl.com".toCharArray()) // use null as second param if you don't have a separate key password + .build(); + + Unirest.config().sslContext(sslContext); + + int response = Unirest.get("https://client.badssl.com/").asEmpty().getStatus(); + assertEquals(200, response); + } + + @Test + void loadWithSSLContextAndProtocol() throws Exception { + SSLContext sslContext = SSLContextBuilder.create() + .loadKeyMaterial(readStore(), "badssl.com".toCharArray()) // use null as second param if you don't have a separate key password + .build(); + + Unirest.config().sslContext(sslContext).protocols("TLSv1.2"); + + int response = Unirest.get("https://client.badssl.com/").asEmpty().getStatus(); + assertEquals(200, response); + } + + @Test + void loadWithSSLContextAndCipher() throws Exception { + SSLContext sslContext = SSLContextBuilder.create() + .loadKeyMaterial(readStore(), "badssl.com".toCharArray()) // use null as second param if you don't have a separate key password + .build(); + + Unirest.config().sslContext(sslContext).ciphers("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"); + + int response = Unirest.get("https://client.badssl.com/").asEmpty().getStatus(); + assertEquals(200, response); + } + + @Test + void loadWithSSLContextAndCipherAndProtocol() throws Exception { + SSLContext sslContext = SSLContextBuilder.create() + .loadKeyMaterial(readStore(), "badssl.com".toCharArray()) // use null as second param if you don't have a separate key password + .build(); + + Unirest.config() + .sslContext(sslContext) + .protocols("TLSv1.2") + .ciphers("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"); + + int response = Unirest.get("https://client.badssl.com/").asEmpty().getStatus(); + assertEquals(200, response); + } + + @Test + void sslHandshakeFailsWhenServerIsReceivingAnUnsupportedCipher() throws Exception { + SSLContext sslContext = SSLContextBuilder.create() + .loadKeyMaterial(readStore(), "badssl.com".toCharArray()) // use null as second param if you don't have a separate key password + .build(); + + Unirest.config() + .sslContext(sslContext) + .protocols("TLSv1.2") + .ciphers("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"); + + GetRequest request = Unirest.get("https://client.badssl.com/"); + assertThrows(UnirestException.class, request::asEmpty); + } + + @Test + void clientPreventsToUseUnsafeProtocol() throws Exception { + SSLContext sslContext = SSLContextBuilder.create() + .loadKeyMaterial(readStore(), "badssl.com".toCharArray()) // use null as second param if you don't have a separate key password + .build(); + + Unirest.config() + .sslContext(sslContext) + .protocols("SSLv3"); + + + fails("https://client.badssl.com/", + SSLHandshakeException.class, + "javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)"); + } + + @Test + void badName() { + fails("https://wrong.host.badssl.com/", + SSLHandshakeException.class, + "javax.net.ssl.SSLHandshakeException: No subject alternative DNS name matching wrong.host.badssl.com found."); + disableSsl(); + canCall("https://wrong.host.badssl.com/"); + } + + @Test + void expired() { + fails("https://expired.badssl.com/", + SSLHandshakeException.class, + "javax.net.ssl.SSLHandshakeException: " + + "PKIX path validation failed: " + + "java.security.cert.CertPathValidatorException: " + + "validity check failed"); + disableSsl(); + canCall("https://expired.badssl.com/"); + } + + @Test + public void whenSelfSignedFailes(){ + fails("https://self-signed.badssl.com/", + SSLHandshakeException.class, + "javax.net.ssl.SSLHandshakeException: " + + "PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: " + + "unable to find valid certification path to requested target"); + + } + + @Test + void selfSigned() throws Exception { + KeyStore ks = readStore(); + + KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + kmf.init(ks, PASSWORD); + + TrustManager[] trustAllCerts = new TrustManager[]{ + new X509TrustManager() { + public void checkClientTrusted(X509Certificate[] certs, String authType) { + } + + public void checkServerTrusted(X509Certificate[] certs, String authType) { + } + + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + } + }; + + var sslContext = SSLContext.getInstance("TLS"); + sslContext.init(kmf.getKeyManagers(), trustAllCerts, new SecureRandom()); + + Unirest.config() + .sslContext(sslContext); + + var result = Unirest.get("https://self-signed.badssl.com/").asString(); + + assertThat(result.getStatus()).isEqualTo(200); + assertThat(result.getBody()).contains("self-signed.
badssl.com"); + + + } + + @Test //issue + public void exampleWithoutJavaClient() throws Exception{ + + // Load the PKCS12 keystore + KeyStore keyStore = readStore(); + + // Init KeyManager with client certificate + KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + kmf.init(keyStore, PASSWORD); + + // Trust all server certs (like --insecure) + TrustManager[] trustAllCerts = new TrustManager[]{ + new X509TrustManager() { + public void checkClientTrusted(X509Certificate[] certs, String authType) { + } + + public void checkServerTrusted(X509Certificate[] certs, String authType) { + } + + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + } + }; + + // Init SSLContext with client cert + trust-all policy + SSLContext sc = SSLContext.getInstance("TLS"); + sc.init(kmf.getKeyManagers(), trustAllCerts, new SecureRandom()); + + // Set default SSL socket factory + HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); + + // Disable hostname verification (like --insecure) + HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> true); + + // Open connection + URL url = new URL("https://self-signed.badssl.com/"); + HttpsURLConnection con = (HttpsURLConnection) url.openConnection(); + con.setRequestMethod("GET"); + + // Read response + int responseCode = con.getResponseCode(); + String statusMessage = con.getResponseMessage(); + System.out.println("Response Code: " + responseCode); + System.out.println("Status Message: " + statusMessage); + + try (BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()))) { + String inputLine; + while ((inputLine = in.readLine()) != null) { + System.out.println(inputLine); + } + } + + con.disconnect(); + } + + @Test + void selfSignedWorksIfDisabled() { + disableSsl(); + canCall("https://self-signed.badssl.com/"); + } + + @Test + void badNameAsync() { + failsAsync("https://wrong.host.badssl.com/", + SSLHandshakeException.class, + "javax.net.ssl.SSLHandshakeException: " + + "No subject alternative DNS name matching wrong.host.badssl.com found."); + disableSsl(); + canCallAsync("https://wrong.host.badssl.com/"); + } + + @Test + void expiredAsync() { + failsAsync("https://expired.badssl.com/", + SSLHandshakeException.class, + "javax.net.ssl.SSLHandshakeException: PKIX path validation failed: java.security.cert.CertPathValidatorException: validity check failed"); + disableSsl(); + canCallAsync("https://expired.badssl.com/"); + } + + @Test + void selfSignedAsync() { + failsAsync("https://self-signed.badssl.com/", + SSLHandshakeException.class, + "javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: " + + "unable to find valid certification path to requested target"); + disableSsl(); + canCallAsync("https://self-signed.badssl.com/"); + } + + private void disableSsl() { + Unirest.config().reset().verifySsl(false); + } + + private void failsAsync(String url, Class exClass, String error) { + try { + var e = Unirest.get(url).asEmptyAsync().get().getParsingError().get().getCause().getCause(); + if (!e.getCause().getClass().isAssignableFrom(exClass)) { + fail("Expected wrong exception type \n Expected: " + exClass + "\n but got " + e.getCause().getClass()); + } + assertEquals(error, e.getMessage(), "Wrong Error Message"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private void fails(String url, Class exClass, String error) { + Exception e = assertThrows(Exception.class, () -> Unirest.get(url).asEmpty()); + if (!e.getCause().getClass().isAssignableFrom(exClass)) { + fail("Expected wrong exception type \n Expected: " + exClass + "\n but got " + e.getCause().getClass()); + } + assertEquals(error, e.getMessage(), "Wrong Error Message"); + } + + private void canCall(String url) { + assertEquals(200, Unirest.get(url).asEmpty().getStatus()); + } + + private void canCallAsync(String url) { + try { + assertEquals(200, Unirest.get(url).asEmptyAsync().get().getStatus()); + } catch (Exception e) { + fail(e.getMessage()); + } + } + + + public static KeyStore readStore() throws Exception { + try (InputStream keyStoreStream = TestUtil.class.getResourceAsStream("/certs/badssl.com-client.p12")) { + KeyStore keyStore = KeyStore.getInstance("PKCS12"); + keyStore.load(keyStoreStream, PASSWORD); + return keyStore; + } + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/ConsumerTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/ConsumerTest.java new file mode 100644 index 000000000..2a94a00ca --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/ConsumerTest.java @@ -0,0 +1,122 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import com.google.common.base.Throwables; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import kong.unirest.core.Unirest; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.junit.jupiter.api.Assertions.*; + +class ConsumerTest extends BddTest { + + Path test = Paths.get("1.jpeg"); + private int status; + private String error; + private boolean asyncDone = false; + + @Override + @AfterEach + public void tearDown() { + super.tearDown(); + asyncDone = false; + status = 0; + File file = test.toFile(); + if(file.exists()){ + file.delete(); + } + } + + @Test + void canSimplyConsumeAResponse() { + Unirest.get(MockServer.GET) + .thenConsume(r -> status = r.getStatus()); + + assertEquals(200, status); + } + + @Test + void downloadAFileAsync() { + Unirest.get(MockServer.BINARYFILE) + .thenConsumeAsync(r -> { + try { + BufferedImage image = ImageIO.read(r.getContent()); + ImageIO.write(image, "jpeg", new File("1.jpeg")); + status = r.getStatus(); + } catch (IOException e) { + error = Throwables.getStackTraceAsString(e); + } finally { + asyncDone = true; + } + }); + while (!asyncDone){ + System.out.print("."); + } + assertNull(error); + assertEquals(200, status); + assertTrue(test.toFile().exists()); + } + + @Test + void downloadAFile() { + Unirest.get(MockServer.BINARYFILE) + .thenConsume(r -> { + try { + BufferedImage image = ImageIO.read(r.getContent()); + ImageIO.write(image, "jpeg", new File("1.jpeg")); + status = r.getStatus(); + } catch (IOException e) { + error = Throwables.getStackTraceAsString(e); + } + }); + + assertNull(error); + assertEquals(200, status); + assertTrue(test.toFile().exists()); + } + + @Test + void canSimplyConsumeAResponseAsync() { + Unirest.get(MockServer.GET) + .thenConsumeAsync(r -> status = r.getStatus()); + + long time = System.currentTimeMillis(); + while (System.currentTimeMillis() - time < 5000) { + if (status != 0) { + break; + } + } + assertEquals(200, status); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/CookieTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/CookieTest.java new file mode 100644 index 000000000..a6638210f --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/CookieTest.java @@ -0,0 +1,243 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.Cookie; +import kong.unirest.core.HttpResponse; +import kong.unirest.core.Unirest; + +import org.junit.jupiter.api.Test; + +import java.time.Month; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; + +import static java.time.LocalDateTime.of; +import static org.junit.jupiter.api.Assertions.*; + +class CookieTest extends BddTest { + + @Test + void canSetCookie(){ + Unirest.get(MockServer.GET) + .cookie("flavor","snickerdoodle") + .cookie("size", "large") + .asObject(RequestCapture.class) + .getBody() + .assertCookie("flavor", "snickerdoodle") + .assertCookie("size", "large"); + } + + @Test + void canSetAsCookie(){ + Unirest.get(MockServer.GET) + .cookie(new Cookie("flavor", "snickerdoodle")) + .cookie(new Cookie("size", "large")) + .asObject(RequestCapture.class) + .getBody() + .assertCookie("flavor", "snickerdoodle") + .assertCookie("size", "large"); + } + + @Test + void canSetAsCookieCollection(){ + Unirest.get(MockServer.GET) + .cookie(Arrays.asList( + new Cookie("flavor", "snickerdoodle"), + new Cookie("size", "large")) + ) + .asObject(RequestCapture.class) + .getBody() + .assertCookie("flavor", "snickerdoodle") + .assertCookie("size", "large"); + } + + + @Test + void willManageCookiesByDefault() { + MockServer.expectCookie("JSESSIONID", "ABC123"); + Unirest.get(MockServer.GET).asEmpty(); + Unirest.get(MockServer.GET) + .asObject(RequestCapture.class) + .getBody() + .assertCookie("JSESSIONID", "ABC123"); + } + + @Test + void canGetCookiesFromTheResponse() { + MockServer.expectCookie("JSESSIONID", "ABC123"); + MockServer.expectCookie("color", "ruby"); + + var response = Unirest.get(MockServer.GET).asEmpty(); + + assertEquals("ABC123", response.getCookies().getNamed("JSESSIONID").getValue()); + assertEquals("ruby", response.getCookies().getNamed("color").getValue()); + } + + @Test + void canGetUnicode() { + MockServer.expectCookie("nepali", "à€«à€šà€•à„€"); + + var response = Unirest.get(MockServer.GET).asEmpty(); + + assertEquals("à€«à€šà€•à„€", response.getCookies().getNamed("nepali").getUrlDecodedValue()); + } + + @Test + void canGetValuesWithBadCharacters() { + MockServer.expectCookie("odd", "1=2;3=4"); + + var response = Unirest.get(MockServer.GET).asEmpty(); + + assertEquals("1=2;3=4", response.getCookies().getNamed("odd").getUrlDecodedValue()); + } + + @Test + void complicatedCookies(){ + expectCoookie(42); + + var response = Unirest.get(MockServer.GET).asEmpty(); + + Cookie back = response.getCookies().getNamed("color"); + assertEquals("blue", back.getValue()); + assertEquals("localhost", back.getDomain()); + assertEquals("/get", back.getPath()); + assertTrue(back.isHttpOnly()); + assertFalse(back.isSecure()); + assertEquals(42, back.getMaxAge()); + } + + @Test + void cookieLifeCycle() { + expectCoookie(42); + var r1 = Unirest.get(MockServer.GET).asObject(RequestCapture.class); + assertNotNull(r1.getCookies().getNamed("color")); + + MockServer.clearCookies(); + expectCoookie(0); + + var r2 = Unirest.get(MockServer.GET).asObject(RequestCapture.class); + assertNotNull(r1.getCookies().getNamed("color")); + r2.getBody().assertCookie("color", "blue"); + + MockServer.clearCookies(); + var r3 = Unirest.get(MockServer.GET).asObject(RequestCapture.class); + r3.getBody().assertNoCookie("color"); + } + + private void expectCoookie(int expiry) { + var cookie = new io.javalin.http.Cookie("color", "blue"); + cookie.setDomain("localhost"); + cookie.setPath("/get"); + cookie.setHttpOnly(true); + cookie.setSecure(false); + cookie.setMaxAge(expiry); + MockServer.expectCookie(cookie); + } + + @Test + void canTurnOffCookieManagement() { + Unirest.config().enableCookieManagement(false); + MockServer.expectCookie("JSESSIONID", "ABC123"); + Unirest.get(MockServer.GET).asEmpty(); + Unirest.get(MockServer.GET) + .asObject(RequestCapture.class) + .getBody() + .assertNoCookie("JSESSIONID"); + } + + @Test + void doubleQuotedValues() { + MockServer.expectCookie(new io.javalin.http.Cookie("foo", "\"bar\"")); + + var res = Unirest.get(MockServer.GET) + .cookie("baz", "\" wut \"") + .asObject(RequestCapture.class); + + res.getBody().assertCookie("baz", " wut "); + + var cookie = res + .getCookies() + .getNamed("foo"); + + assertEquals("bar", cookie.getValue()); + } + + @Test + void canSetDefaultCookie() { + Unirest.config().addDefaultCookie("flavor","snickerdoodle"); + + Unirest.get(MockServer.GET) + .cookie("size", "large") + .asObject(RequestCapture.class) + .getBody() + .assertCookie("flavor", "snickerdoodle") + .assertCookie("size", "large"); + } + + @Test + void canSetDefaultCookieAsFullCookieObj() { + Unirest.config().addDefaultCookie(new Cookie("flavor","snickerdoodle")); + + Unirest.get(MockServer.GET) + .cookie("size", "large") + .asObject(RequestCapture.class) + .getBody() + .assertCookie("flavor", "snickerdoodle") + .assertCookie("size", "large"); + } + + @Test + void stringCookieParsing() { + // The server is going to return a cookie set to expire in 2140 + MockServer.addResponseHeader("Set-Cookie", getCookieValue(of(2140, Month.APRIL, 2, 4, 20, 0).atZone(ZoneId.of("UTC")))); + + // We make a request and the cookie is returned and we have the cookie + // It should now be in our store + var r1 = Unirest.get(MockServer.GET).asObject(RequestCapture.class); + assertNotNull(r1.getCookies().getNamed("color")); + + } + + @Test + void newAgeCookies() { + Unirest.config().cookieSpec("standard"); + MockServer.addResponseHeader("Set-Cookie", "cookie_name=blah;Max-Age=86400;Expires=Wed, 9 Dec 2220 20:26:05 GMT;Path=/;Domain=localhost;HTTPOnly"); + + var response = Unirest.get(MockServer.GET).asObject(RequestCapture.class); + assertNotNull(response.getCookies().getNamed("cookie_name")); + + var r2 = Unirest.get(MockServer.GET).asObject(RequestCapture.class); + r2.getBody().assertCookie("cookie_name","blah"); + } + + private String getCookieValue(ZonedDateTime dt) { + var date = dt.format(DateTimeFormatter.ofPattern("EEE, dd-MMM-yyyy HH:mm:ss zzz")); + return String.format("color=blue; Path=/get; Max-Age=6000; Domain=localhost; Expires=%s; HttpOnly", date); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/CustomClientTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/CustomClientTest.java new file mode 100644 index 000000000..fd9579d4e --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/CustomClientTest.java @@ -0,0 +1,55 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.Client; +import kong.unirest.core.HttpRequest; +import kong.unirest.core.HttpResponse; +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; + +import java.util.function.Function; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class CustomClientTest extends BddTest { + + @Test + void settingACustomClient() { + var client = mock(Client.class);; + + var mock = mock(HttpResponse.class); + when(client.request(any(HttpRequest.class), + any(Function.class), + any(Class.class))).thenReturn(mock); + Unirest.config().httpClient(client); + + assertEquals(mock, Unirest.get("http://localhost/getme").asEmpty()); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/CustomObjectMapperTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/CustomObjectMapperTest.java new file mode 100644 index 000000000..bacc95fc3 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/CustomObjectMapperTest.java @@ -0,0 +1,67 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.ObjectMapper; +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; + +class CustomObjectMapperTest extends BddTest { + + private ObjectMapper customOm; + + @Override + @BeforeEach + public void setUp() { + super.setUp(); + customOm = Mockito.spy(ObjectMapper.class); + } + + @Test + void canSetCustomObjectMapperOnConfig() { + Unirest.config().setObjectMapper(customOm); + + Unirest.get(MockServer.GET) + .asObject(RequestCapture.class); + + verify(customOm).readValue(anyString(), eq(RequestCapture.class)); + } + + @Test + void canSetCustomObjectMapperOnRequest() { + Unirest.get(MockServer.GET) + .withObjectMapper(customOm) + .asObject(RequestCapture.class); + + verify(customOm).readValue(anyString(), eq(RequestCapture.class)); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/DefectTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/DefectTest.java new file mode 100644 index 000000000..d9307d60a --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/DefectTest.java @@ -0,0 +1,72 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.Unirest; +import kong.unirest.core.json.JSONObject; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class DefectTest extends BddTest { + + @Test + void hashOnLinksDoNotMessUpUri() { + Unirest.get(MockServer.GET + "?a=1&b=2#some_location") + .asObject(RequestCapture.class) + .getBody() + .assertParam("a", "1") + .assertParam("b", "2"); + } + + @Test + void nullAndObjectValuesInMap() { + Map queryParams = new HashMap<>(); + queryParams.put("foo", null); + queryParams.put("baz", "qux"); + + Unirest.get(MockServer.GET) + .queryString(queryParams) + .asObject(RequestCapture.class) + .getBody() + .assertParam("foo", "") + .assertParam("baz", "qux") + .assertQueryString("foo&baz=qux"); + } + + @Test @Disabled + void trySomeoneElsesGZip() throws Exception { + JSONObject body = Unirest.get("http://httpbin.org/gzip") + .asJsonAsync().get().getBody().getObject(); + + assertEquals(true, body.getBoolean("gzipped")); + assertEquals("gzip", body.getJSONObject("headers").getString("Accept-Encoding")); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/DownloadProgressTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/DownloadProgressTest.java new file mode 100644 index 000000000..22977f0d0 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/DownloadProgressTest.java @@ -0,0 +1,86 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.io.TempDir; + +import java.io.File; + +class DownloadProgressTest extends BddTest { + @TempDir + File targeFolder; + + private TestMonitor monitor; + + + @BeforeEach + public void setUp() { + super.setUp(); + this.monitor = new TestMonitor(); + } + + @Test + void canAddUploadProgress() { + Unirest.get(MockServer.BINARYFILE) + .downloadMonitor(monitor) + .asFile(targeFolder.getPath() + "/spidey.jpg"); + + monitor.assertSpideyFileDownload("spidey.jpg"); + } + + @Test + void canAddUploadProgressAsync() throws Exception { + Unirest.get(MockServer.BINARYFILE) + .downloadMonitor(monitor) + .asFileAsync(targeFolder.getPath() + "/spidey.jpg") + .get(); + + monitor.assertSpideyFileDownload("spidey.jpg"); + } + + @Test + void canAddUploadProgressWithBytes() { + Unirest.get(MockServer.BINARYFILE) + .downloadMonitor(monitor) + .asBytes(); + + monitor.assertSpideyFileDownload("body"); + } + + @Test + void canAddUploadProgressWithBytesAsync() throws Exception { + Unirest.get(MockServer.BINARYFILE) + .downloadMonitor(monitor) + .asBytesAsync() + .get(); + + monitor.assertSpideyFileDownload("body"); + } + +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/ErrorParsingTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/ErrorParsingTest.java new file mode 100644 index 000000000..184bd8675 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/ErrorParsingTest.java @@ -0,0 +1,163 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import com.fasterxml.jackson.annotation.JsonProperty; +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; +import tools.jackson.core.StreamWriteFeature; + +import static org.junit.jupiter.api.Assertions.*; + + +class ErrorParsingTest extends BddTest { + private boolean errorCalled; + + @Test + void passParsingErrorsOnInFailure() { + MockServer.setStringResponse("not json"); + + Unirest.get(MockServer.GET) + .asObject(RequestCapture.class) + .ifFailure(String.class, r -> + { + assertTrue(r.getParsingError().isPresent()); + assertTrue(r.getParsingError().get().getMessage().contains("jackson")); + assertEquals("not json", r.getParsingError().get().getOriginalBody()); + } + ) + .ifSuccess(s -> fail("no")); + } + + @Test + void parsingAnAlternativeErrorObject() { + MockServer.setJsonAsResponse(new ErrorThing("boom!")); + + var e = Unirest.get(MockServer.ERROR_RESPONSE) + .asObject(RequestCapture.class) + .mapError(ErrorThing.class); + + assertErrorThing(e); + } + + @Test + void mapTheErrorToAString() { + MockServer.setJsonAsResponse(new ErrorThing("boom!")); + + var e = Unirest.get(MockServer.ERROR_RESPONSE) + .asObject(RequestCapture.class) + .mapError(String.class); + + assertEquals("{\"message\":\"boom!\"}", e); + } + + @Test + void parsingAnAlternativeErrorObject_StringBody() { + MockServer.setJsonAsResponse(new ErrorThing("boom!")); + + var e = Unirest.get(MockServer.ERROR_RESPONSE) + .asString() + .mapError(ErrorThing.class); + + assertErrorThing(e); + } + + @Test + void parsingAnAlternativeErrorObject_JsonBody() { + MockServer.setJsonAsResponse(new ErrorThing("boom!")); + + var e = Unirest.get(MockServer.ERROR_RESPONSE) + .asJson() + .mapError(ErrorThing.class); + + assertErrorThing(e); + } + + @Test + void ifNoErrorThenGetTheRegularBody() { + var error = Unirest.get(MockServer.GET) + .asObject(RequestCapture.class) + .mapError(ErrorThing.class); + + assertNull(error); + } + + @Test + void failsIfErrorResponseCantBeMapped() { + var om = new JacksonObjectMapper(); + om.om = om.om.rebuild() + .configure(StreamWriteFeature.IGNORE_UNKNOWN, false) + .configure(tools.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true) + .build(); + + MockServer.setJsonAsResponse(new ErrorThing("boom!")); + + var request = Unirest.get(MockServer.ERROR_RESPONSE) + .withObjectMapper(om) + .asObject(RequestCapture.class); + + var error = request.mapError(NotTheError.class); + + assertNull(error.merp); + assertEquals("{\"message\":\"boom!\"}", request.getParsingError().get().getOriginalBody()); + } + + @Test + void mapTheErrorWithAFunction() { + MockServer.setJsonAsResponse(new ErrorThing("boom!")); + + errorCalled = false; + Unirest.get(MockServer.ERROR_RESPONSE) + .asObject(RequestCapture.class) + .ifFailure(ErrorThing.class, e -> { + assertEquals(400, e.getStatus()); + assertErrorThing(e.getBody()); + errorCalled = true; + }).ifSuccess(e -> {throw new AssertionError("No");}); + + assertTrue(errorCalled); + } + + @Test + void willKeepBodyAroundOnFailures_WhenJacksonIsPassive() { + MockServer.setJsonAsResponse(new ErrorThing("boom!")); + + Unirest.get(MockServer.ERROR_RESPONSE) + .asObject(RequestCapture.class) + .mapError(ErrorThing.class) + .assertError("boom!"); + + } + + private void assertErrorThing(ErrorThing e) { + assertEquals("boom!", e.getMessage()); + } + + public static class NotTheError { + @JsonProperty("merp") + public String merp; + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/ErrorThing.java b/unirest-bdd-tests/src/test/java/BehaviorTests/ErrorThing.java new file mode 100644 index 000000000..b71f3aa43 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/ErrorThing.java @@ -0,0 +1,49 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ErrorThing { + + private final String message; + + @JsonCreator + public ErrorThing(@JsonProperty("message") String message){ + this.message = message; + } + + public String getMessage() { + return message; + } + + public void assertError(String s) { + assertEquals(s, message); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/ExecutorTests.java b/unirest-bdd-tests/src/test/java/BehaviorTests/ExecutorTests.java new file mode 100644 index 000000000..7fa537909 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/ExecutorTests.java @@ -0,0 +1,54 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ExecutorTests extends BddTest { + @Test + void canSetCustomExecutor() { + var executor = new CustomExecutor(); + Unirest.config().executor(executor); + Unirest.get(MockServer.GET).asEmpty(); + assertTrue(executor.wasUsed); + } + + private class CustomExecutor implements Executor { + Executor e = Executors.newSingleThreadExecutor(); + boolean wasUsed; + @Override + public void execute(Runnable command) { + wasUsed = true; + e.execute(command); + } + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/Foo.java b/unirest-bdd-tests/src/test/java/BehaviorTests/Foo.java new file mode 100644 index 000000000..2806b3fe0 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/Foo.java @@ -0,0 +1,58 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import com.google.common.base.MoreObjects; + +import java.util.Objects; + +public class Foo { + public String bar; + + public Foo(){ } + + public Foo(String bar) { + this.bar = bar; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("bar",bar).toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) {return true;} + if (o == null || getClass() != o.getClass()) {return false;} + Foo foo = (Foo) o; + return Objects.equals(bar, foo.bar); + } + + @Override + public int hashCode() { + return Objects.hash(bar); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/FormPostingTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/FormPostingTest.java new file mode 100644 index 000000000..a3e34c921 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/FormPostingTest.java @@ -0,0 +1,203 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.nio.charset.StandardCharsets; + +import static java.util.Arrays.asList; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class FormPostingTest extends BddTest { + + @ParameterizedTest + @ValueSource(strings = {"a&b", "a=b", "a+b", "a%b", "first name", "cafĂ©", "æ—„æœŹèȘž", "a%26b"}) + void formFieldNamesSurvivePosting(String name) { + RequestCapture capture = Unirest.post(MockServer.POST) + .field(name, "v +&=%") + .asObject(RequestCapture.class) + .getBody(); + + capture.assertUrlEncodedContent().assertParam(name, "v +&=%"); + assertEquals(1, capture.params.size()); + } + + @Test + void encodedFormFieldNamesSurviveAsyncPosting() throws Exception { + RequestCapture capture = Unirest.post(MockServer.POST) + .field("a&b", "one") + .field("a&b", "two") + .asObjectAsync(RequestCapture.class) + .get() + .getBody(); + + capture.assertUrlEncodedContent() + .assertParam("a&b", "one") + .assertParam("a&b", "two"); + assertEquals(2, capture.params.size()); + } + + @Test + void testFormFields() { + Unirest.post(MockServer.POST) + .header("accept", "application/json") + .field("param1", "value1") + .field("param2", "bye") + .asObject(RequestCapture.class) + .getBody() + .assertParam("param1", "value1") + .assertParam("param2", "bye") + .assertUrlEncodedContent(); + } + + @Test + void formPostAsync() { + Unirest.post(MockServer.POST) + .header("accept", "application/json") + .field("param1", "value1") + .field("param2", "bye") + .asJsonAsync(new MockCallback<>(this, r -> { + RequestCapture req = parse(r); + req.assertParam("param1", "value1"); + req.assertParam("param2", "bye"); + req.assertUrlEncodedContent(); + })); + + assertAsync(); + } + + @Test + void testAsyncCustomContentTypeAndFormParams() { + Unirest.post(MockServer.POST) + .header("accept", "application/json") + .header("Content-Type", "application/x-www-form-urlencoded") + .field("name", "Mark") + .field("hello", "world") + .asJsonAsync(new MockCallback<>(this, r -> parse(r) + .assertParam("name", "Mark") + .assertParam("hello", "world") + .assertContentType("application/x-www-form-urlencoded") + )); + + assertAsync(); + } + + @Test + void testPostArray() { + Unirest.post(MockServer.POST) + .field("name", "Mark") + .field("name", "Tom") + .asObject(RequestCapture.class) + .getBody() + .assertUrlEncodedContent() + .assertParam("name", "Mark") + .assertParam("name", "Tom"); + } + + @Test + void testPostUTF8() { + Unirest.post(MockServer.POST) + .header("accept", "application/json") + .field("param3", "こんにづは") + .asObject(RequestCapture.class) + .getBody() + .assertUrlEncodedContent() + .assertParam("param3", "こんにづは"); + } + + @Test + void testPostCollection() { + Unirest.post(MockServer.POST) + .field("name", asList("Mark", "Tom")) + .asObject(RequestCapture.class) + .getBody() + .assertUrlEncodedContent() + .assertParam("name", "Mark") + .assertParam("name", "Tom"); + } + + @Test + void nullMapDoesntBomb() { + Unirest.post(MockServer.POST) + .queryString("foo","bar") + .fields(null) + .asObject(RequestCapture.class) + .getBody() + .assertUrlEncodedContent() + .assertParam("foo", "bar"); + } + + @Test + void testDelete() { + Unirest.delete(MockServer.DELETE) + .field("name", "mark") + .field("foo", "bar") + .asObject(RequestCapture.class) + .getBody() + .assertUrlEncodedContent() + .assertParam("name", "mark") + .assertParam("foo", "bar"); + } + + @Test + void testChangingEncodingToForms(){ + Unirest.post(MockServer.POST) + .charset(StandardCharsets.US_ASCII) + .field("foo", "bar") + .asObject(RequestCapture.class) + .getBody() + .assertRawContentType("application/x-www-form-urlencoded; charset=US-ASCII") + .assertParam("foo", "bar") + .assertCharset(StandardCharsets.US_ASCII); + } + + @Test + void canNotIncludeCharset(){ + Unirest.post(MockServer.POST) + .noCharset() + .field("foo", "bar") + .asObject(RequestCapture.class) + .getBody() + .assertContentType("application/x-www-form-urlencoded"); + } + + @Test + void testChangingEncodingAfterMovingToForm(){ + Unirest.post(MockServer.POST) + .field("foo", "bar") + .charset(StandardCharsets.US_ASCII) + .asObject(RequestCapture.class) + .getBody() + .assertContentType("application/x-www-form-urlencoded", "charset", "US-ASCII") + .assertParam("foo", "bar") + .assertCharset(StandardCharsets.US_ASCII); + } + +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/GZipTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/GZipTest.java new file mode 100644 index 000000000..63f671e31 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/GZipTest.java @@ -0,0 +1,85 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.HttpResponse; +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +class GZipTest extends BddTest { + + @Test + void emptyGzip() { + HttpResponse result = Unirest.post(MockServer.EMPTY_GZIP) + .asString(); + result.getParsingError().ifPresent(e -> { + e.printStackTrace(); + }); + assertFalse(result.getParsingError().isPresent()); + assertEquals("", result.getBody()); + } + + @Test + void testGzip() { + HttpResponse resp = Unirest.get(MockServer.GZIP) + .queryString("zipme", "up") + .asObject(RequestCapture.class); + + resp.getBody() + .assertParam("zipme", "up") + .assertHeader("Accept-Encoding","gzip"); + + assertEquals(0, resp.getHeaders().get("Content-Encoding").size()); + } + + @Test + void testGzipAsync() throws Exception { + HttpResponse resp = Unirest.get(MockServer.GZIP) + .queryString("zipme", "up") + .asObjectAsync(RequestCapture.class) + .get(); + + resp.getBody() + .assertParam("zipme", "up") + .assertHeader("Accept-Encoding", "gzip"); + + assertEquals(0, resp.getHeaders().get("Content-Encoding").size()); + } + + @Test + void canDisableGZip() throws Exception { + Unirest.config().requestCompression(false); + + Unirest.get(MockServer.GET) + .asObjectAsync(RequestCapture.class) + .get() + .getBody() + .assertNoHeader("Accept-Encoding"); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/GenericMappingTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/GenericMappingTest.java new file mode 100644 index 000000000..d1fca3828 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/GenericMappingTest.java @@ -0,0 +1,58 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.HttpResponse; +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class GenericMappingTest extends BddTest { + + @Test + void canMapBody() { + MockServer.setStringResponse("123456"); + + int body = Unirest.get(MockServer.GET).asString().mapBody(Integer::valueOf); + + assertEquals(123456, body); + } + + @Test + void canMapTheEntireResponseIntoAnotherResponse() { + MockServer.setStringResponse("123456"); + MockServer.addResponseHeader("cheese","cheddar"); + + var response = Unirest.get(MockServer.GET) + .asString() + .map(Integer::valueOf); + + assertEquals(123456, response.getBody().intValue()); + assertEquals(200, response.getStatus()); + assertEquals("cheddar", response.getHeaders().getFirst("cheese")); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/GetResponse.java b/unirest-bdd-tests/src/test/java/BehaviorTests/GetResponse.java new file mode 100644 index 000000000..526c7170b --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/GetResponse.java @@ -0,0 +1,44 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class GetResponse { + + @JsonProperty("url") + private String url; + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/HeaderAsserts.java b/unirest-bdd-tests/src/test/java/BehaviorTests/HeaderAsserts.java new file mode 100644 index 000000000..70c3c5992 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/HeaderAsserts.java @@ -0,0 +1,192 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.Splitter; +import com.google.common.collect.*; +import io.javalin.http.Context; +import org.assertj.core.data.MapEntry; + +import java.util.Base64; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +public class HeaderAsserts { + public ArrayListMultimap headers = ArrayListMultimap.create(); + + public HeaderAsserts(){} + + public HeaderAsserts(Context req) { + Collections.list(req.req().getHeaderNames()) + .forEach(name -> Collections.list(req.req().getHeaders(name)) + .forEach(value -> headers.put(name, new HeaderValue(value)))); + } + + public HeaderAsserts(Map values){ + values.forEach((k,v) -> headers.put(k, new HeaderValue(v))); + } + + public void assertNoHeader(String s) { + assertFalse(headers.containsKey(s), "Should Have No Header " + s); + } + + public HeaderAsserts assertContainsHeader(String key){ + TestUtil.assertMultiMap(headers).containsKeys(key); + return this; + } + + public HeaderAsserts assertHeader(String key, String... value) { + assertContainsHeader(key); + assertThat(headers.get(key)) + .extracting(HeaderValue::getValue) + .contains(value); + return this; + } + + public HeaderAsserts assertBasicAuth(String username, String password) { + TestUtil.assertMultiMap(headers).containsKeys("Authorization"); + var value = headers.get("Authorization").get(0); + assertThat(value.getValue()).as("Missing value scope of Basic").contains("Basic "); + String decoded = new String(Base64.getDecoder().decode(value.getValue().replace("Basic ", ""))); + assertThat(decoded).isEqualTo(username + ":" + password); + return this; + } + + public HeaderAsserts assertHeaderSize(String name, int size) { + assertEquals(size, headers.get(name).size()); + return this; + } + + public HeaderAsserts assertMultiPartContentType() { + var h = headers.get("Content-Type"); + assertEquals(1, h.size(), "Expected exactly 1 Content-Type header"); + var value = h.get(0); + value.assertMainValue("multipart/form-data"); + value.assertHasParam("boundary"); + value.assertParam("charset", "UTF-8"); + return this; + } + + public HeaderAsserts assertHeaderWithParam(String headerKey, String headerValue, String paramKey, String paramValue) { + TestUtil.assertMultiMap(headers).containsKeys(headerKey); + var value = headers.get(headerKey).get(0); + value.assertMainValue(headerValue); + value.assertParam(paramKey, paramValue); + return this; + } + + public HeaderAsserts assertRawValue(String key, String value) { + assertThat(headers.get(key)) + .isNotEmpty() + .extracting(HeaderValue::rawValue) + .contains(value); + return this; + } + + public HeaderValue getFirst(String name) { + TestUtil.assertMultiMap(headers).containsKeys(name); + return headers.get(name) + .stream() + .findFirst() + .orElseThrow(() -> new AssertionError("Missing any values for header " + name)); + } + + public static class HeaderValue { + @JsonIgnore private static final Splitter partSplitter = Splitter.on(";").trimResults().omitEmptyStrings(); + @JsonIgnore private static final Splitter paramSplitter = Splitter.on("=").trimResults().omitEmptyStrings(); + @JsonProperty("rawValue") + private String rawValue; + @JsonProperty("value") + private String value; + @JsonProperty("params") + private ArrayListMultimap params = ArrayListMultimap.create(); + + public HeaderValue(){} + + public HeaderValue(String value) { + this.rawValue = value; + if(value.contains(";")){ + int pos=0; + for(String part : partSplitter.split(value)){ + if(pos == 0){ + this.value = part; + } else if (part.contains("=")) { + var param = paramSplitter.splitToList(part); + params.put(param.get(0), param.get(1)); + } + pos++; + } + } else { + this.value = value; + } + } + + public String getValue() { + return value; + } + + @Override + public String toString(){ + return value; + } + + public HeaderValue assertHasParam(String name) { + TestUtil.assertMultiMap(params) + .as("Header Param") + .containsKeys(name); + return this; + } + + public HeaderValue assertMainValue(String expectedValue) { + assertEquals(expectedValue, value); + return this; + } + + public HeaderValue assertParam(String name, String value) { + TestUtil.assertMultiMap(params) + .as("Header Param") + .contains(MapEntry.entry(name, value)); + return this; + } + + public String rawValue() { + return rawValue; + } + + public void assertRawValue(String value) { + assertThat(rawValue).isEqualTo(value); + } + } + +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/HeaderAssertsTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/HeaderAssertsTest.java new file mode 100644 index 000000000..b72febcc9 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/HeaderAssertsTest.java @@ -0,0 +1,40 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import org.junit.jupiter.api.Test; + +import java.util.Map; + +class HeaderAssertsTest { + + @Test + void canAssertHeaderWithParts() { + new HeaderAsserts(Map.of("foo", "bar; baz=qux; zip=zap;;")) + .assertHeader("foo", "bar") + .assertHeaderWithParam("foo", "bar", "baz", "qux"); + } +} \ No newline at end of file diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/HeaderTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/HeaderTest.java new file mode 100644 index 000000000..d1f083e3a --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/HeaderTest.java @@ -0,0 +1,448 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.*; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.util.Base64; +import java.util.Collection; + +import static BehaviorTests.TestUtil.mapOf; +import static org.junit.jupiter.api.Assertions.*; + +class HeaderTest extends BddTest { + + private String value = "one"; + + @Test + void contentLengthIsSetWithBodies() { + Unirest.post(MockServer.POST) + .body("do do do do") + .asObject(RequestCapture.class) + .getBody() + .assertHeader("Content-Length", "11"); + } + + @Test + void testHeadersOnGetRequests() { + Unirest.get(MockServer.GET) + .header("user-agent", "hello-world") + .accept("application/cheese-wiz") + .asObject(RequestCapture.class) + .getBody() + .assertHeader("User-Agent", "hello-world") + .assertHeader("Accept","application/cheese-wiz"); + } + + @Test + void testBasicAuth() { + Unirest.get(MockServer.GET) + .basicAuth("user", "password1!") + .asObject(RequestCapture.class) + .getBody() + .assertHeader("Authorization", "Basic dXNlcjpwYXNzd29yZDEh") + .assertBasicAuth("user", "password1!"); + } + + @Test + void unicodeBasicAuth() { + Unirest.get(MockServer.GET) + .basicAuth("こんにづは", "こんにづは") + .asObject(RequestCapture.class) + .getBody() + .assertHeader("Authorization", "Basic 44GT44KT44Gr44Gh44GvOuOBk+OCk+OBq+OBoeOBrw==") + .assertBasicAuth("こんにづは", "こんにづは"); + } + + @Test + void testDefaultHeaders() { + Unirest.config().setDefaultHeader("X-Custom-Header", "hello"); + Unirest.config().setDefaultHeader("user-agent", "foobar"); + + var jsonResponse = Unirest.get(MockServer.GET).asJson(); + + parse(jsonResponse) + .assertHeader("X-Custom-Header", "hello") + .assertHeader("User-Agent", "foobar"); + + jsonResponse = Unirest.get(MockServer.GET).asJson(); + parse(jsonResponse) + .assertHeader("X-Custom-Header", "hello") + .assertHeader("User-Agent", "foobar"); + + Unirest.config().clearDefaultHeaders(); + + jsonResponse = Unirest.get(MockServer.GET).asJson(); + parse(jsonResponse) + .assertNoHeader("X-Custom-Header"); + } + + @Test + void testCaseInsensitiveHeaders() { + var request = Unirest.get(MockServer.GET) + .header("Name", "Marco"); + + assertEquals(1, request.getHeaders().size()); + assertEquals("Marco", request.getHeaders().get("name").get(0)); + assertEquals("Marco", request.getHeaders().get("NAme").get(0)); + assertEquals("Marco", request.getHeaders().get("Name").get(0)); + + parse(request.asJson()) + .assertHeader("Name", "Marco"); + + request = Unirest.get(MockServer.GET).header("Name", "Marco").header("Name", "John"); + assertEquals(1, request.getHeaders().size()); + assertEquals("Marco", request.getHeaders().get("name").get(0)); + assertEquals("John", request.getHeaders().get("name").get(1)); + assertEquals("Marco", request.getHeaders().get("NAme").get(0)); + assertEquals("John", request.getHeaders().get("NAme").get(1)); + assertEquals("Marco", request.getHeaders().get("Name").get(0)); + assertEquals("John", request.getHeaders().get("Name").get(1)); + } + + @Test + void headersOnMultipart() { + Unirest.post(MockServer.POST) + .field("one","a") + .accept("application/json") + .basicAuth("tony","tuna") + .header("cheese", "cheddar") + .contentType("application/xml") + .asObject(RequestCapture.class) + .getBody() + .assertHeader("Accept", "application/json") + .assertBasicAuth("tony","tuna") + .assertHeader("cheese", "cheddar") + .assertHeader("Content-Type", "application/xml"); + + } + + @Test + void canPassHeadersAsMap() { + Unirest.post(MockServer.POST) + .headers(mapOf("one", "foo", "two", "bar", "three", null)) + .asObject(RequestCapture.class) + .getBody() + .assertHeader("one", "foo") + .assertHeader("two", "bar") + .assertHeader("three", ""); + } + + @Test + void basicAuthOnPosts() { + Unirest.post(MockServer.POST) + .basicAuth("user", "test") + .asObject(RequestCapture.class) + .getBody() + .assertHeader("Authorization", "Basic dXNlcjp0ZXN0") + .assertBasicAuth("user", "test"); + } + + @Test + void canSetDefaultBasicAuth() { + Unirest.config().setDefaultBasicAuth("bob", "pass"); + Unirest.config().setDefaultBasicAuth("user", "test"); + + Unirest.post(MockServer.POST) + .asObject(RequestCapture.class) + .getBody() + .assertHeader("Authorization", "Basic dXNlcjp0ZXN0") + .assertBasicAuth("user", "test"); + } + + @Test + void canOverrideDefaultBasicAuth() { + Unirest.config().setDefaultBasicAuth("bob", "pass"); + + Unirest.post(MockServer.POST) + .basicAuth("user", "test") + .asObject(RequestCapture.class) + .getBody() + .assertHeader("Authorization", "Basic dXNlcjp0ZXN0") + .assertBasicAuth("user", "test"); + + Unirest.post(MockServer.POST) + .asObject(RequestCapture.class) + .getBody() + .assertHeader("Authorization", "Basic Ym9iOnBhc3M=") + .assertBasicAuth("bob", "pass"); + } + + @Test + void willNotCacheBasicAuth() { + Unirest.get(MockServer.GET) + .basicAuth("george","guitar") + .asObject(RequestCapture.class) + .getBody() + .assertBasicAuth("george","guitar"); + + Unirest.get(MockServer.GET) + .asObject(RequestCapture.class) + .getBody() + .assertNoHeader("Authorization"); + + Unirest.get(MockServer.GET) + .basicAuth("ringo","drums") + .asObject(RequestCapture.class) + .getBody() + .assertBasicAuth("ringo","drums"); + } + + @Test + void willNotCacheHeadersAccrossRequests() { + Unirest.get(MockServer.GET) + .header("foo", "bar") + .asObject(RequestCapture.class) + .getBody() + .assertHeader("foo", "bar"); + + Unirest.get(MockServer.GET) + .asObject(RequestCapture.class) + .getBody() + .assertNoHeader("foo"); + + Unirest.get(MockServer.GET) + .header("baz", "qux") + .asObject(RequestCapture.class) + .getBody() + .assertHeader("baz", "qux"); + } + + @Test + void doesNotCacheAcrossTypes(){ + Unirest.get(MockServer.GET) + .basicAuth("user1","pass1") + .asObject(RequestCapture.class) + .getBody() + .assertBasicAuth("user1","pass1"); + + Unirest.post(MockServer.POST) + .basicAuth("user2", "pass2") + .asObject(RequestCapture.class) + .getBody() + .assertBasicAuth("user2", "pass2"); + } + + @Test @Disabled + void doesNotCacheAuthAcrossDomains(){ + Unirest.get(MockServer.GET) + .basicAuth("user1","pass1") + .asObject(RequestCapture.class) + .getBody() + .assertBasicAuth("user1","pass1"); + + var bin = Unirest.post("http://httpbin.org/post") + .basicAuth("user2", "pass2") + .asJson() + .getBody(); + + String header = bin.getObject().getJSONObject("headers").getString("Authorization"); + assertNotNull(header, "Authorization Header Missing"); + String credentials = header.replace("Basic ",""); + assertEquals("user2" + ":" + "pass2", new String(Base64.getDecoder().decode(credentials))); + } + + @Test //https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 + void canHaveTheSameHeaderAddedTwice() { + Unirest.config().setDefaultHeader("x-fruit", "orange"); + + Unirest.get(MockServer.GET) + .header("x-fruit", "apple") + .asObject(RequestCapture.class) + .getBody() + .assertHeaderSize("x-fruit", 2) + .assertHeader("x-fruit", "orange") + .assertHeader("x-fruit", "apple"); + } + + @Test + void canReplaceAHeader() { + Unirest.config().setDefaultHeader("foo", "bar"); + Unirest.get(MockServer.GET) + .headerReplace("foo", "qux") + .headerReplace("fruit", "mango") + .asObject(RequestCapture.class) + .getBody() + .assertHeaderSize("foo", 1) + .assertHeaderSize("fruit", 1) + .assertHeader("foo", "qux") + .assertHeader("fruit", "mango"); + } + + @Test + void setVsAddDefaultHeaders() { + Unirest.config().setDefaultHeader("foo", "bar") + .setDefaultHeader("foo", "qux") + .addDefaultHeader("fruit", "mango") + .addDefaultHeader("fruit", "orange") + .addDefaultHeader("colour","red") + .setDefaultHeader("colour","blue") + .addDefaultHeader("colour", "yellow"); + + Unirest.get(MockServer.GET) + .asObject(RequestCapture.class) + .getBody() + .assertHeaderSize("foo", 1) + .assertHeader("foo", "qux") + .assertHeaderSize("fruit", 2) + .assertHeader("fruit", "mango") + .assertHeader("fruit", "orange") + .assertHeaderSize("colour", 2) + .assertHeader("colour","blue") + .assertHeader("colour", "yellow"); + } + + @Test + void canSetAHeaderAsASupplier() { + Unirest.config().setDefaultHeader("trace", () -> value); + + Unirest.get(MockServer.GET) + .asObject(RequestCapture.class) + .getBody() + .assertHeader("trace", "one"); + + value = "two"; + + Unirest.get(MockServer.GET) + .asObject(RequestCapture.class) + .getBody() + .assertHeader("trace", "two"); + } + + @Test + void nullValueTests() { + Unirest.get(MockServer.GET) + .header("foo", null) + .asObject(RequestCapture.class) + .getBody() + .assertHeader("foo",""); + } + + @Test + void nullTests() { + Unirest.get(MockServer.GET) + .header("foo","bar") + .headers(null) + .asObject(RequestCapture.class) + .getBody() + .assertHeader("foo","bar"); + } + + @Test + void replaceHeadersTests() { + Unirest.config().addDefaultHeader("foo", "default"); + Unirest.get(MockServer.GET) + .header("foo","bar") + .headersReplace(mapOf("foo", "replace", "two", "bar")) + .asObject(RequestCapture.class) + .getBody() + .assertHeader("foo","replace") + .assertHeader("two","bar"); + } + + @Test + void multipleContentHeaders() { + Unirest.post(MockServer.GET) + .contentType("application/json") + .header("Content-Type", "xml") + .asObject(RequestCapture.class) + .getBody() + .assertHeaderSize("Content-Type", 2); + } + + @Test + void contentTypeHeadersCanBeOverwritten() { + Unirest.post(MockServer.POST) + .field("one","a") + .contentType("application/json") + .asObject(RequestCapture.class) + .getBody() + .assertHeaderSize("Content-Type", 1) + .assertContentType("application/json"); + } + + @Test + void defaultContentTypes() { + Unirest.post(MockServer.POST) + .field("one","a") + .asObject(RequestCapture.class) + .getBody() + .assertHeaderSize("Content-Type", 1) + .assertRawContentType("application/x-www-form-urlencoded; charset=utf-8"); + } + + @Test + void nullHeaderValues() { + Unirest.get(MockServer.GET) + .header("foo", null) + .asObject(RequestCapture.class) + .getBody() + .assertHeader("foo", ""); + } + + @Test + void getHeadersInSummary() { + Unirest.config() + .setDefaultHeader("cheese", "wiz") + .interceptor(new Interceptor() { + @Override + public void onRequest(HttpRequest request, Config config) { + request.header("beatles", "Ringo"); + } + }); + + var headers = Unirest.get(MockServer.GET) + .header("foo", "bar") + .header("baz", "qux") + .asEmpty() + .getRequestSummary() + .getHeaders(); + + assertEquals(4, headers.size()); + assertContains(headers, "cheese", "wiz"); + assertContains(headers, "foo", "bar"); + assertContains(headers, "baz", "qux"); + assertContains(headers, "beatles", "ringo"); + } + + @Test + void passInMediaTypeAsAcceptsAndContentType() { + Unirest.post(MockServer.POST) + .accept(ContentType.IMAGE_JPEG) + .contentType(ContentType.APPLICATION_JSON) + .asObject(RequestCapture.class) + .getBody() + .assertAccepts(ContentType.IMAGE_JPEG) + .assertContentType(ContentType.APPLICATION_JSON); + } + + private void assertContains(Collection
headers, String key, String value) { + assertTrue(headers.stream().anyMatch(h -> key.equalsIgnoreCase(key) && value.equalsIgnoreCase(value)), + "Missing header " + key + " with value " + value); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/HostsHeaderTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/HostsHeaderTest.java new file mode 100644 index 000000000..111a7b240 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/HostsHeaderTest.java @@ -0,0 +1,84 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.HttpResponse; +import kong.unirest.core.Unirest; +import kong.unirest.core.UnirestException; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class HostsHeaderTest extends BddTest { + + /** + * # + * # Allow restricted HTTP request headers + * # + * # By default, the following request headers are not allowed to be set by user code + * # in HttpRequests: "connection", "content-length", "expect", "host" and "upgrade". + * # The 'jdk.httpclient.allowRestrictedHeaders' property allows one or more of these + * # headers to be specified as a comma separated list to override the default restriction. + * # The names are case-insensitive and white-space is ignored (removed before processing + * # the list). Note, this capability is mostly intended for testing and isn't expected + * # to be used in real deployments. Protocol errors or other undefined behavior is likely + * # to occur when using them. The property is not set by default. + * # Note also, that there may be other headers that are restricted from being set + * # depending on the context. This includes the "Authorization" header when the + * # relevant HttpClient has an authenticator set. These restrictions cannot be + * # overridden by this property. + * # + * # jdk.httpclient.allowRestrictedHeaders=host + * # + */ + @Test @Disabled + void willHonorHostsHeaders() { + Unirest.get(MockServer.ALTGET) + .header("Host", "localhost") + .asObject(RequestCapture.class) + .getBody() + .assertUrl("http://localhost/get"); + } + + @Test + void cannotNormallyOverrideHost() { + assertThatThrownBy(() -> Unirest.get(MockServer.ALTGET).header("Host", "localhost").asEmpty()) + .isInstanceOf(UnirestException.class) + .hasRootCauseMessage("restricted header name: \"Host\""); + } + + @Test @Disabled + void canBuildCustomHost() { + var s = Unirest.get("https://104.154.89.105/") + .header("Host", "sha512.badssl.com") + .asString(); + + assertEquals(200, s.getStatus()); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/Http2Test.java b/unirest-bdd-tests/src/test/java/BehaviorTests/Http2Test.java new file mode 100644 index 000000000..f11a14faf --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/Http2Test.java @@ -0,0 +1,99 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.HttpResponse; +import kong.unirest.core.JsonNode; +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.net.http.HttpClient; + +public class Http2Test extends BddTest { + + @Test @Disabled + void canMakeHttp2Requests() { + Unirest.config().version(HttpClient.Version.HTTP_2); + + HttpResponse httpResponse = Unirest.get("https://nghttp2.org/httpbin/get") + .accept("application/json") + //.header("wu","tang") + .asJson(); + + System.out.println("httpResponse = " + httpResponse); + } + + @Test + void sendsHeadersForHttp2() { + Unirest.config().version(HttpClient.Version.HTTP_2); + + Unirest.get(MockServer.GET) + .asObject(RequestCapture.class) + .getBody() + .assertHeader("Connection", "Upgrade, HTTP2-Settings") + .assertHeader("Upgrade", "h2c") + .assertHeader("HTTP2-Settings", TestUtil.Matchers.isBase64Encoded()); + } + + @Test + void dontSendHttp2HeadersForHttp1() { + Unirest.config().version(HttpClient.Version.HTTP_1_1); + + Unirest.get(MockServer.GET) + .asObject(RequestCapture.class) + .getBody() + .assertNoHeader("Connection") + .assertNoHeader("Upgrade") + .assertNoHeader("HTTP2-Settings"); + } + + @Test + void overrideConfigPerRequest() { + Unirest.config().version(HttpClient.Version.HTTP_1_1); + + Unirest.get(MockServer.GET) + .version(HttpClient.Version.HTTP_2) + .asObject(RequestCapture.class) + .getBody() + .assertHeader("Connection", "Upgrade, HTTP2-Settings") + .assertHeader("Upgrade", "h2c") + .assertHeader("HTTP2-Settings", TestUtil.Matchers.isBase64Encoded()); + } + + @Test + void overrideConfigPerRequest2() { + Unirest.config().version(HttpClient.Version.HTTP_2); + + Unirest.get(MockServer.GET) + .version(HttpClient.Version.HTTP_1_1) + .asObject(RequestCapture.class) + .getBody() + .assertNoHeader("Connection") + .assertNoHeader("Upgrade") + .assertNoHeader("HTTP2-Settings"); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/InputStreamTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/InputStreamTest.java new file mode 100644 index 000000000..974d4815f --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/InputStreamTest.java @@ -0,0 +1,64 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; + +public class InputStreamTest extends BddTest { + @Test + void canSendInputStreamAsBody() { + Unirest.post(MockServer.POST) + .body(new ByteArrayInputStream("Hi Mom".getBytes())) + .asObject(RequestCapture.class) + .getBody() + .assertBody("Hi Mom"); + } + + @Test + void canSendInputStreamAsBodyAsync() throws Exception { + Unirest.post(MockServer.POST) + .body(new ByteArrayInputStream("Hi Mom".getBytes())) + .asObjectAsync(RequestCapture.class) + .get() + .getBody() + .assertBody("Hi Mom"); + } + + @Test + void canSendInputStreamAsBodyAsyncWithCallback() throws Exception { + Unirest.post(MockServer.POST) + .body(new ByteArrayInputStream("Hi Mom".getBytes())) + .asObjectAsync(RequestCapture.class, r -> { + r.getBody().assertBody("Hi Mom"); + asyncSuccess(); + }); + + assertAsync(); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/InterceptorTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/InterceptorTest.java new file mode 100644 index 000000000..25c193ab6 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/InterceptorTest.java @@ -0,0 +1,170 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ExecutionException; + +import static com.google.common.collect.Sets.newHashSet; +import static BehaviorTests.TestUtil.rezFile; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class InterceptorTest extends BddTest { + + private UniInterceptor interceptor; + + + @BeforeEach + public void setUp() { + super.setUp(); + interceptor = new UniInterceptor("x-custom", "foo"); + } + + @Test + void canAddInterceptor() { + Unirest.config().interceptor(interceptor); + Unirest.get(MockServer.GET).asObject(RequestCapture.class); + + interceptor.cap.assertHeader("x-custom", "foo"); + assertEquals(MockServer.GET, interceptor.reqSum.getUrl()); + } + + @Test + void canAddTwoInterceptor() { + Unirest.config().interceptor(interceptor); + Unirest.config().interceptor(new UniInterceptor("fruit", "grapes")); + Unirest.get(MockServer.GET).asObject(RequestCapture.class); + + interceptor.cap.assertHeader("x-custom", "foo"); + interceptor.cap.assertHeader("fruit", "grapes"); + } + + @Test + void canAddInterceptorToAsync() throws ExecutionException, InterruptedException { + Unirest.config().interceptor(interceptor); + + Unirest.get(MockServer.GET) + .asObjectAsync(RequestCapture.class) + .get(); + + interceptor.cap.assertHeader("x-custom", "foo"); + } + + @Test + void totalFailure() throws Exception { + Unirest.config().httpClient(TestUtil.getFailureClient()).interceptor(interceptor); + + var ex = assertThrows(UnirestException.class, () -> Unirest.get(MockServer.GET).asEmpty()); + assertEquals("java.io.IOException: " + "Something horrible happened", ex.getMessage()); + } + + @Test + void canReturnEmptyResultRatherThanThrow() throws Exception { + Unirest.config().httpClient(TestUtil.getFailureClient()).interceptor(interceptor); + interceptor.failResponse = true; + + var response = Unirest.get(MockServer.GET).asString(); + + assertEquals(542, response.getStatus()); + assertEquals("Something horrible happened", response.getStatusText()); + } + + @Test + void totalAsyncFailure_Recovery() throws Exception { + interceptor.failResponse = true; + Unirest.config() + .httpClient(TestUtil.getFailureAsyncClient()) + .interceptor(interceptor); + + var response = Unirest.get(MockServer.GET).asStringAsync().get(); + + assertEquals(542, response.getStatus()); + } + + @Test + void loggingBodyPartsExample() { + var values = new HashSet<>(); + Unirest.config().interceptor(new Interceptor() { + @Override + public void onRequest(HttpRequest request, Config config) { + request.getBody().ifPresent(b -> + b.multiParts().forEach(part -> + values.add(part.toString()))); + } + }); + + Unirest.post(MockServer.POST) + .field("fruit", "apples") + .field("file", rezFile("/spidey.jpg")) + .asEmpty(); + + assertEquals(newHashSet("file=spidey.jpg","fruit=apples"), values); + } + + + + private class UniInterceptor implements Interceptor { + RequestCapture cap; + HttpRequestSummary reqSum; + boolean failResponse; + boolean dothrow; + private String name; + private String value; + + public UniInterceptor(String name, String value){ + this.name = name; + this.value = value; + } + + @Override + public void onRequest(HttpRequest request, Config config) { + if(dothrow){ + throw new RuntimeException("Something Happened"); + } + request.header(name, value); + } + + @Override + public void onResponse(HttpResponse response, HttpRequestSummary request, Config config) { + cap = (RequestCapture)response.getBody(); + reqSum = request; + } + + @Override + public HttpResponse onFail(Exception e, HttpRequestSummary request, Config config) { + if(failResponse){ + return new FailedResponse(e); + } + return Interceptor.super.onFail(e, request, config); + } + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/JacksonObjectMapper.java b/unirest-bdd-tests/src/test/java/BehaviorTests/JacksonObjectMapper.java new file mode 100644 index 000000000..839f37a1f --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/JacksonObjectMapper.java @@ -0,0 +1,161 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import tools.jackson.core.JacksonException; +import tools.jackson.core.JsonGenerator; +import tools.jackson.core.JsonParser; +import tools.jackson.core.StreamWriteFeature; +import tools.jackson.databind.*; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.databind.module.SimpleModule; +import tools.jackson.datatype.guava.GuavaModule; +import kong.unirest.core.*; +import kong.unirest.core.json.JSONObject; + +import java.io.File; +import java.io.InputStream; + +public class JacksonObjectMapper implements kong.unirest.core.ObjectMapper { + + public tools.jackson.databind.ObjectMapper om; + + public JacksonObjectMapper(tools.jackson.databind.ObjectMapper om){ + this.om = om; + } + + public JacksonObjectMapper(){ + JsonMapper.Builder builder = JsonMapper.builderWithJackson2Defaults(); + builder.addModule(new GuavaModule()); + SimpleModule simpleModule = new SimpleModule(); + simpleModule.addSerializer(JsonPatchItem.class, new PatchSerializer()); + simpleModule.addSerializer(JsonPatch.class, new JsonPatchSerializer()); + simpleModule.addDeserializer(JsonPatchItem.class, new PatchDeserializer()); + simpleModule.addDeserializer(JsonPatch.class, new JsonPatchDeSerializer()); + simpleModule.addSerializer(HttpMethod.class, new HttpMethodSerializer()); + simpleModule.addDeserializer(HttpMethod.class, new HttpMethodDeSerializer()); + builder.configure(StreamWriteFeature.IGNORE_UNKNOWN, true); + builder.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + builder.addModule(simpleModule); + this.om = builder.build(); + } + + public T readValue(File f, Class valueType){ + try { + return om.readValue(f, valueType); + } catch (JacksonException e) { + throw new RuntimeException(e); + } + } + + @Override + public T readValue(String value, Class valueType) { + try { + return om.readValue(value, valueType); + } catch (JacksonException e) { + throw new UnirestException(e); + } + } + + @Override + public T readValue(String value, GenericType genericType) { + try { + return om.readValue(value, om.constructType(genericType.getType())); + } catch (JacksonException e) { + throw new UnirestException(e); + } + } + + @Override + public String writeValue(Object value) { + try { + return om.writeValueAsString(value); + } catch (JacksonException e) { + throw new UnirestException(e); + } + } + + public T readValue(InputStream rawBody, Class as) { + try { + return om.readValue(rawBody, as); + } catch (JacksonException e) { + throw new RuntimeException(e); + } + } + + public T readValue(byte[] content, Class clazz) { + try { + return om.readValue(content, clazz); + } catch (JacksonException e) { + throw new RuntimeException(e); + } + } + + public static class JsonPatchSerializer extends ValueSerializer{ + @Override + public void serialize(JsonPatch patch, JsonGenerator jgen, SerializationContext ctxt) throws JacksonException { + jgen.writeRawValue(patch.toString()); + } + } + + public static class JsonPatchDeSerializer extends ValueDeserializer{ + @Override + public JsonPatch deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) { + String s = jsonParser.readValueAsTree().toString(); + return new JsonPatch(s); + } + } + + public static class PatchSerializer extends ValueSerializer { + @Override + public void serialize(JsonPatchItem jwk, JsonGenerator jgen, SerializationContext ctxt) throws JacksonException { + jgen.writeRaw(jwk.toString()); + } + } + + public static class PatchDeserializer extends ValueDeserializer { + @Override + public JsonPatchItem deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) { + String s = jsonParser.readValueAsTree().toString(); + return new JsonPatchItem(new JSONObject(s)); + } + } + + public static class HttpMethodSerializer extends ValueSerializer { + @Override + public void serialize(HttpMethod httpMethod, JsonGenerator jgen, SerializationContext ctxt) throws JacksonException { + jgen.writeString(httpMethod.name()); + } + } + + public static class HttpMethodDeSerializer extends ValueDeserializer { + @Override + public HttpMethod deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) { + String s = jsonParser.readValueAs(String.class); + return HttpMethod.valueOf(s); + } + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/JacksonObjectMapperTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/JacksonObjectMapperTest.java new file mode 100644 index 000000000..f1bbc923c --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/JacksonObjectMapperTest.java @@ -0,0 +1,67 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + + +import kong.unirest.core.JsonPatch; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +class JacksonObjectMapperTest { + + private final JacksonObjectMapper om = new JacksonObjectMapper(); + + @Test + void jsonPatch() throws JSONException { + var patch = new JsonPatch(); + patch.add("/foo", "bar"); + patch.add("/baz", "qux"); + + String expectStr = patch.toString(); + String actualStr = om.writeValue(patch); + + JSONAssert.assertEquals(expectStr, + actualStr, + true); + } + + @Test + void jsonPatchInRequestCapture() throws JSONException { + var patch = new JsonPatch(); + patch.add("/foo", "bar"); + patch.add("/baz", "qux"); + + var rc = new RequestCapture(); + rc.setPatch(patch); + + String actualStr = om.writeValue(rc); + System.out.println("actualStr = " + actualStr); + JSONAssert.assertEquals("{}", + actualStr, + false); + } +} \ No newline at end of file diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/JankyProxy.java b/unirest-bdd-tests/src/test/java/BehaviorTests/JankyProxy.java new file mode 100644 index 000000000..1dccb4319 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/JankyProxy.java @@ -0,0 +1,159 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import java.io.*; +import java.net.*; +/** + * + * @author jcgonzalez.com + * + */ +public class JankyProxy { + public static ThreadProxy thread; + public static void runServer(String host, int remoteport, int localport) { + try { + // and the local port that we listen for connections on + // Print a start-up message + System.out.println("Starting proxy for " + host + ":" + remoteport + + " on port " + localport); + ServerSocket server = new ServerSocket(localport); + thread = new ThreadProxy(server, host, remoteport); + } catch (Exception e) { + System.err.println(e); + System.err.println("Usage: java ProxyMultiThread " + + " "); + } + } + + public static boolean wasUsed(){ + return thread.wasUsedForClientToServer; + } + + public static void shutdown(){ + if(thread != null){ + try { + thread.shutdown(); + }catch (Exception e){ + e.printStackTrace(); + } + } + } +} +/** + * Handles a socket connection to the proxy server from the client and uses 2 + * threads to proxy between server and client + * + * @author jcgonzalez.com + * + */ +class ThreadProxy extends Thread { + private Socket sClient; + private ServerSocket socket; + private final String SERVER_URL; + private final int SERVER_PORT; + public boolean wasUsedForClientToServer; + + ThreadProxy(ServerSocket socket, String ServerUrl, int ServerPort) { + this.SERVER_URL = ServerUrl; + this.SERVER_PORT = ServerPort; + this.socket = socket; + this.start(); + } + @Override + public void run() { + try { + sClient = socket.accept(); + final byte[] request = new byte[1024]; + byte[] reply = new byte[4096]; + final InputStream inFromClient = sClient.getInputStream(); + final OutputStream outToClient = sClient.getOutputStream(); + Socket client = null, server = null; + // connects a socket to the server + try { + server = new Socket(SERVER_URL, SERVER_PORT); + } catch (IOException e) { + PrintWriter out = new PrintWriter(new OutputStreamWriter(outToClient)); + out.flush(); + System.out.println("SERVER_URL = " + SERVER_URL + " : " + SERVER_PORT); + throw new RuntimeException(e); + } + // a new thread to manage streams from server to client (DOWNLOAD) + final InputStream inFromServer = server.getInputStream(); + final OutputStream outToServer = server.getOutputStream(); + // a new thread for uploading to the server + new Thread(() -> { + int bytes_read; + try { + while ((bytes_read = inFromClient.read(request)) != -1) { + outToServer.write(request, 0, bytes_read); + outToServer.flush(); + wasUsedForClientToServer = true; + } + } catch (IOException ignored) { + } + try { + outToServer.close(); + } catch (IOException e) { + e.printStackTrace(); + } + }).start(); + // current thread manages streams from server to client (DOWNLOAD) + int bytes_read; + try { + while ((bytes_read = inFromServer.read(reply)) != -1) { + outToClient.write(reply, 0, bytes_read); + outToClient.flush(); + //TODO CREATE YOUR LOGIC HERE + } + } catch (IOException e) { + // e.printStackTrace(); + } finally { + try { + if (server != null) + server.close(); + if (client != null) + client.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + outToClient.close(); + sClient.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void shutdown() { + try { + socket.close(); + } catch (IOException e) { + e.printStackTrace(); + } + interrupt(); + } +} \ No newline at end of file diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/JsonCoreTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/JsonCoreTest.java new file mode 100644 index 000000000..88c6490f2 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/JsonCoreTest.java @@ -0,0 +1,40 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.json.CoreFactory; +import kong.unirest.modules.gson.GsonEngine; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class JsonCoreTest { + + @Test + void getsFromServiceLocator() { + assertThat(CoreFactory.findEngineWithServiceLocator()).isInstanceOf(GsonEngine.class); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/JsonPatchTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/JsonPatchTest.java new file mode 100644 index 000000000..9b4b2f1fb --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/JsonPatchTest.java @@ -0,0 +1,159 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import com.google.common.collect.ImmutableMap; +import kong.unirest.core.Unirest; +import kong.unirest.core.json.JSONArray; +import kong.unirest.core.json.JSONObject; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + + +import static kong.unirest.core.JsonPatchOperation.*; + +class JsonPatchTest extends BddTest { + + @Test + void canAddThings() { + Unirest.jsonPatch(MockServer.PATCH) + .add("/some/path", "a value") + .add("/another/path", 42) + .add("/third/path", true) + .asObject(RequestCapture.class) + .getBody() + .assertContentType("application/json-patch+json") + .assertJsonPatch(add, "/some/path", "a value") + .assertJsonPatch(add, "/another/path", 42) + .assertJsonPatch(add, "/third/path", true); + } + + @Test + void canRemoveThings() { + Unirest.jsonPatch(MockServer.PATCH) + .remove("/some/path") + .remove("/another/path") + .remove("/third/path") + .asObject(RequestCapture.class) + .getBody() + .assertContentType("application/json-patch+json") + .assertJsonPatch(remove, "/some/path", null) + .assertJsonPatch(remove, "/another/path", null) + .assertJsonPatch(remove, "/third/path", null); + } + + @Test + void canReplaceThings() { + Unirest.jsonPatch(MockServer.PATCH) + .replace("/some/path", "a value") + .replace("/another/path", 42) + .replace("/third/path", true) + .asObject(RequestCapture.class) + .getBody() + .assertContentType("application/json-patch+json") + .assertJsonPatch(replace, "/some/path", "a value") + .assertJsonPatch(replace, "/another/path", 42) + .assertJsonPatch(replace, "/third/path", true); + } + + @Test + void canTestThings() { + Unirest.jsonPatch(MockServer.PATCH) + .test("/some/path", "a value") + .test("/another/path", 42) + .test("/third/path", true) + .asObject(RequestCapture.class) + .getBody() + .assertContentType("application/json-patch+json") + .assertJsonPatch(test, "/some/path", "a value") + .assertJsonPatch(test, "/another/path", 42) + .assertJsonPatch(test, "/third/path", true); + } + + @Test + void canUseJsonForValues() { + Unirest.jsonPatch(MockServer.PATCH) + .add("/some/path", new JSONObject().put("id", "foo")) + .asObject(RequestCapture.class) + .getBody() + .assertJsonPatch(add, "/some/path", new JSONObject().put("id", "foo")); + } + + @Test + void lotsOfDifferentWaysToMakeObjects() { + JSONObject basicJson = new JSONObject().put("foo", "bar"); + + Unirest.jsonPatch(MockServer.PATCH) + .add("/stringArrays", new String[]{"foo, bar"}) + .add("/maps", ImmutableMap.of("foo", "bar")) + .add("/jsonObjects", basicJson) + .add("/jsonArrays", new JSONArray().put(basicJson)) + .asObject(RequestCapture.class) + .getBody() + //.assertJsonPatch(add, "/stringArrays", new String[]{"foo, bar"}) + .assertJsonPatch(add, "/maps", basicJson) + .assertJsonPatch(add, "/jsonObjects", basicJson) + .assertJsonPatch(add, "/jsonArrays", new JSONArray().put(basicJson)) + ; + } + + @Test + void canMoveObjects() { + Unirest.jsonPatch(MockServer.PATCH) + .move("/old/location", "/new/location") + .asObject(RequestCapture.class) + .getBody() + .assertJsonPatch(move, "/new/location", "/old/location"); + } + + @Test + void canCopyObjects() { + Unirest.jsonPatch(MockServer.PATCH) + .copy("/old/location", "/new/location") + .asObject(RequestCapture.class) + .getBody() + .assertJsonPatch(copy, "/new/location", "/old/location"); + } + + @Test + void thatsSomeValidJson() throws Exception { + String patch = Unirest.jsonPatch(MockServer.PATCH) + .add("/fruits/-", "Apple") + .remove("/bugs") + .replace("/lastname", "Flintstone") + .test("/firstname", "Fred") + .move("/old/location", "/new/location") + .copy("/original/location", "/new/location") + .asObject(RequestCapture.class) + .getBody() + .body; + + var expected = TestUtil.getResource("test-json-patch.json"); + + JSONAssert.assertEquals(expected, patch, true); + } + +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/MetricsTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/MetricsTest.java new file mode 100644 index 000000000..ad1b15aad --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/MetricsTest.java @@ -0,0 +1,191 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.HttpRequestSummary; + +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.function.Function; + +import static BehaviorTests.MockServer.*; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.*; + +class MetricsTest extends BddTest { + @Test + void canSetAMetricObjectToInstramentUnirest() { + var metric = configureMetric(); + + Unirest.get(GET).asEmpty(); + Unirest.get(GET).asEmpty(); + Unirest.get(PASSED_PATH_PARAM) + .routeParam("params", "foo") + .queryString("fruit", "orange") + .asEmpty(); + + assertEquals(2, metric.routes.get(GET).size()); + assertEquals(1, metric.routes.get("http://localhost:4567/get/foo/passed?fruit=orange").size()); + } + + @Test + void canGetParamedUrlInSummary() { + var metric = configureMetric(HttpRequestSummary::getRawPath); + + Unirest.get(PASSED_PATH_PARAM) + .routeParam("params", "foo") + .queryString("fruit", "orange") + .asEmpty(); + + assertEquals(1, metric.routes.get("http://localhost:4567/get/{params}/passed").size()); + } + + @Test + void canGetToMethod() { + var metric = configureMetric(s -> s.getHttpMethod().name()); + + Unirest.get(GET).asEmpty(); + Unirest.get(GET).asEmpty(); + Unirest.post(POST).asEmpty(); + Unirest.delete(DELETE).asEmpty(); + + assertEquals(2, metric.routes.get("GET").size()); + assertEquals(1, metric.routes.get("DELETE").size()); + } + + @Test + void amountOfTimeBetweenRequestAndResponseDoesNotIncludeDeserialization() { + var metric = configureMetric(); + + Unirest.get(GET).asObject(r -> { + assertEquals(1, metric.routes.get(GET).size()); + return null; + }); + } + + @Test + void canGetInformationOnStatus() { + var metric = configureMetric(HttpRequestSummary::getUrl); + + Unirest.get(GET).asEmpty(); + Unirest.get(REDIRECT).asEmpty(); + Unirest.get(REDIRECT).asEmpty(); + Unirest.get(ERROR_RESPONSE).asEmpty(); + + assertEquals(1L, metric.countResponses(200)); + assertEquals(2L, metric.countResponses(301)); + assertEquals(1L, metric.countResponses(400)); + } + + @Test + void metricsOnAsyncRequests() throws Exception { + var metric = configureMetric(); + + Unirest.get(GET).asEmptyAsync().get(); + + assertEquals(1L, metric.countResponses(200)); + } + + @Test + void metricsOnAsyncMethodsWithCallbacks() { + var metric = configureMetric(); + + Unirest.get(GET).asEmptyAsync(r -> asyncSuccess()); + + assertAsync(); + assertEquals(1L, metric.countResponses(200)); + } + + @Test + void errorHandling() throws Exception { + var metric = new MyMetric(HttpRequestSummary::getUrl); + Unirest.config().reset().httpClient(TestUtil.getFailureClient()).instrumentWith(metric); + + assertThatThrownBy(() -> Unirest.get(GET).asEmpty()); + + assertEquals("Something horrible happened", metric.routes.get(GET).get(0).e.getMessage()); + } + + @Test @Disabled + void errorHandling_async() throws Exception { + MyMetric metric = configureMetric(); + + + try { + Unirest.get("http://localhost:0000").asEmptyAsync().get(); + }catch (Exception e){ + + } + + assertEquals("Connection refused", metric.routes.get("http://localhost:0000").get(0).e.getMessage()); + assertEquals(1, metric.routes.get("http://localhost:0000").size()); + } + + long exTime; + + @Test + void metricAsALambda() { + exTime = 0; + Unirest.config().instrumentWith((s) -> { + long startNanos = System.nanoTime(); + return (r,e) -> exTime = System.nanoTime() - startNanos; + }); + + Unirest.get(GET).asEmpty(); + + assertTrue(exTime > 0L); + } + + @Test + void showWhatJavalinDoes() { + var map = Unirest.get(JAVALIN) + .routeParam("spark", "joy") + .queryString("food", "hamberders") + .queryString("colour", "red") + .asObject(HashMap.class) + .getBody(); + + assertEquals("localhost:4567", map.get("host()")); + assertEquals("/sparkle/joy/yippy", map.get("uri()")); // this is different from what the Spark doc says. + assertEquals("http://localhost:4567/sparkle/joy/yippy", map.get("url()")); + assertEquals("", map.get("contextPath()")); + assertEquals("/sparkle/joy/yippy", map.get("pathInfo()")); + assertEquals("food=hamberders&colour=red", map.get("queryString()")); + } + + private MyMetric configureMetric() { + return configureMetric(HttpRequestSummary::getUrl); + } + + private MyMetric configureMetric(Function keyFunction) { + var metric = new MyMetric(keyFunction); + Unirest.config().followRedirects(false).instrumentWith(metric); + return metric; + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/MockCallback.java b/unirest-bdd-tests/src/test/java/BehaviorTests/MockCallback.java new file mode 100644 index 000000000..531f61a25 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/MockCallback.java @@ -0,0 +1,80 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.Callback; +import kong.unirest.core.HttpResponse; +import kong.unirest.core.JsonNode; +import kong.unirest.core.UnirestException; + +import java.util.function.Consumer; + +public class MockCallback implements Callback { + + public static MockCallback json(BddTest test){ + return new MockCallback<>(test); + } + + private final BddTest test; + private Consumer> onSuccess = r -> {}; + private Consumer onFail = f -> {}; + private Runnable onCancel = () -> {}; + + public MockCallback(BddTest test){ + this.test = test; + } + + public MockCallback(BddTest test, Consumer> onSuccess){ + this.test = test; + this.onSuccess = onSuccess; + } + + public MockCallback onFail(Consumer onFail){ + this.onFail = onFail; + return this; + } + + public MockCallback onCancel(Consumer onFail){ + this.onFail = onFail; + return this; + } + + @Override + public void completed(HttpResponse response) { + onSuccess.accept(response); + test.asyncSuccess(); + } + + @Override + public void failed(UnirestException e) { + test.asyncFail(e.getMessage()); + } + + @Override + public void cancelled() { + test.asyncFail("canceled"); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/MockServer.java b/unirest-bdd-tests/src/test/java/BehaviorTests/MockServer.java new file mode 100644 index 000000000..696d25946 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/MockServer.java @@ -0,0 +1,450 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + +package BehaviorTests; + +import com.google.common.base.Strings; +import io.javalin.Javalin; +import io.javalin.http.Context; +import io.javalin.http.Cookie; +import io.javalin.http.HttpStatus; +import io.javalin.websocket.WsConfig; +import jakarta.servlet.ServletOutputStream; +import org.eclipse.jetty.util.UrlEncoded; +import org.jetbrains.annotations.NotNull; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.zip.GZIPOutputStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + + +public class MockServer { + + + public static int timesCalled; + private static int pages = 1; + private static int onPage = 1; + private static int retryTimes = 0; + private static int retryStatus = 429; + private static String retrySeconds = ""; + private static final List> responseHeaders = new ArrayList<>(); + private static final List cookies = new ArrayList<>(); + private static final WebSocketHandler ws = new WebSocketHandler(); + private static final JacksonObjectMapper om = new JacksonObjectMapper(); + private static String responseBody; + public static final int PORT = 4567; + public static final String HOST = "http://localhost:" + PORT; + public static final String WEBSOCKET = "ws://localhost:" + PORT + "/websocket"; + public static final String SSE = HOST + "/sse"; + public static final String WINDOWS_LATIN_1_FILE = HOST + "/public/data/cp1250.txt"; + public static final String REDIRECT = HOST + "/redirect"; + public static final String JAVALIN = HOST + "/sparkle/{spark}/yippy"; + public static final String BINARYFILE = HOST + "/binary"; + public static final String NOBODY = HOST + "/nobody"; + public static final String PAGED = HOST + "/paged"; + public static final String POST = HOST + "/post"; + public static final String GET = HOST + "/get"; + public static final String ERROR_RESPONSE = HOST + "/error"; + public static final String QUERY = HOST + "/query"; + public static final String DELETE = HOST + "/delete"; + public static final String GZIP = HOST + "/gzip"; + public static final String EMPTY_GZIP = HOST + "/empty-gzip"; + public static final String PATCH = HOST + "/patch"; + public static final String INVALID_REQUEST = HOST + "/invalid"; + public static final String PASSED_PATH_PARAM = GET + "/{params}/passed"; + public static final String PASSED_PATH_PARAM_MULTI = PASSED_PATH_PARAM + "/{another}"; + public static final String CHEESE = HOST + "/cheese"; + public static final String ALTGET = "http://127.0.0.1:" + PORT + "/get"; + public static final String ECHO_RAW = HOST + "/raw"; + public static final String TIMEOUT = HOST + "/timeout"; + public static final String BASICAUTH = HOST + "/basic"; + private static Javalin app; + private static int errorCode = 400; + private static RequestCapture lastRequest; + + + public static void setJsonAsResponse(Object o) { + responseBody = om.writeValue(o); + } + + public static void reset() { + responseBody = null; + responseHeaders.clear(); + cookies.clear(); + pages = 1; + onPage = 1; + timesCalled = 0; + WebSocketHandler.reset(); + retryTimes = 0; + errorCode = 400; + lastRequest = null; + } + + static { + app = Javalin.create(c -> { + c.staticFiles.add("public/"); + c.routes.before(ctx -> { + timesCalled++; + lastRequest = new RequestCapture(ctx); + }); + c.routes.before("/basic", ctx -> { + if (ctx.header("Authorization") == null) { + ctx.header("WWW-Authenticate", "Basic realm=\"User Visible Realm\", charset=\"UTF-8\""); + ctx.status(HttpStatus.UNAUTHORIZED); + } + }); + c.routes.ws("/websocket", ws); + c.routes.sse("/sse", new TestSSEConsumer()); + c.routes.delete("/delete", MockServer::jsonResponse); + c.routes.get("/sparkle/{spark}/yippy", MockServer::sparkle); + c.routes.post("/post", MockServer::jsonResponse); + c.routes.get("/get", MockServer::jsonResponse); + c.routes.query("/query", MockServer::jsonResponse); + c.routes.get("/gzip", MockServer::gzipResponse); + c.routes.post("/empty-gzip", MockServer::emptyGzipResponse); + c.routes.get("/redirect", MockServer::redirect); + c.routes.post("/redirect", MockServer::redirect); + c.routes.patch("/patch", MockServer::jsonResponse); + c.routes.get("/invalid", MockServer::inValid); + c.routes.options("/get", MockServer::jsonResponse); + c.routes.get("/nobody", MockServer::nobody); + c.routes.head("/get", MockServer::jsonResponse); + c.routes.put("/post", MockServer::jsonResponse); + c.routes.get("/get/{params}/passed", MockServer::jsonResponse); + c.routes.get("/get/{params}/passed/{another}", MockServer::jsonResponse); + c.routes.get("/proxy", MockServer::proxiedResponse); + c.routes.get("/binary", MockServer::file); + c.routes.get("/paged", MockServer::paged); + c.routes.post("/paged", MockServer::paged); + c.routes.post("/raw", MockServer::echo); + c.routes.get("/error", MockServer::error); + c.routes.get("/hello", MockServer::helloWOrld); + c.routes.get("/timeout", MockServer::timeout); + c.routes.get("/basic", MockServer::basic); + c.routes.error(404, MockServer::notFound); + }).start(PORT); + Runtime.getRuntime().addShutdownHook(new Thread(app::stop)); + try { + new CountDownLatch(1).await(2, TimeUnit.SECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + private static void basic(@NotNull Context context) { + if (!(context.status().getCode() == 401)) { + jsonResponse(context); + } + } + + private static void timeout(Context context) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + + } + context.result("Hello World"); + } + + + private static void helloWOrld(Context c) { + c.result("Hello World"); + } + + + public static void main(String[] args){ + + } + + private static void sparkle(Context request) { + Map sparks = new HashMap<>(); + sparks.put("contentType()", request.contentType()); + sparks.put("contextPath()", request.contextPath()); + sparks.put("host()", request.host()); + sparks.put("ip()", request.ip()); + sparks.put("pathInfo()", request.req().getPathInfo()); + sparks.put("port()", String.valueOf(request.port())); + sparks.put("protocol()", request.protocol()); + sparks.put("scheme()", request.scheme()); + sparks.put("servletPath()", request.req().getServletPath()); + //sparks.put("requestMethod()", request.me()); + sparks.put("uri()", request.req().getRequestURI()); + sparks.put("url()", request.url()); + sparks.put("userAgent()", request.userAgent()); + sparks.put("queryString()", request.queryString()); + request.result(om.writeValue(sparks)); + } + + private static void error(Context request) { + request.status(errorCode); + request.result(Strings.nullToEmpty(responseBody)); + } + + private static void echo(Context request) { + request.result(request.body()); + } + + private static void paged(Context context) { + if (pages > onPage) { + onPage++; + context.header("nextPage", PAGED + "?page=" + onPage); + } + jsonResponse(context); + } + + private static void notFound(Context context) { + RequestCapture value = getRequestCapture(context); + value.setStatus(404); + context.status(404); + context.result(om.writeValue(value)); + } + + private static Object file(Context context) throws Exception { + File f = TestUtil.rezFile("/spidey.jpg"); + context.contentType("application/octet-stream"); + context.header("Content-Disposition", "attachment;filename=image.jpg"); + context.header("Content-Length", String.valueOf(f.length())); + context.status(200); + final ServletOutputStream out = context.res().getOutputStream(); + final FileInputStream in = new FileInputStream(f); + in.transferTo(out); + out.close(); + in.close(); + return null; + } + + private static void nobody(Context request) { + request.status(200); + } + + private static void redirect(Context request) { + request.redirect(GET, HttpStatus.MOVED_PERMANENTLY); + } + + private static void inValid(Context request) { + request.status(400); + request.result("You did something bad"); + } + + private static Object emptyGzipResponse(Context response) throws Exception { + response.res().setHeader("Content-Encoding", "gzip"); + response.res().setContentType("application/json"); + response.res().setStatus(200); + response.res().getOutputStream().close(); + return null; + } + + private static void gzipResponse(Context context) { + context.header("Content-Encoding", "gzip"); + jsonResponse(context, true); + } + + private static void proxiedResponse(Context context) { + simpleResponse(context) + .orElseGet(() -> { + RequestCapture value = getRequestCapture(context); + value.setIsProxied(true); + return om.writeValue(value); + }); + } + + private static Optional simpleResponse(Context context) { + cookies.forEach(c -> context.cookie(c)); + responseHeaders.forEach(h -> context.res().addHeader(h.key, h.value)); + + if (responseBody != null) { + return Optional.of(responseBody); + } + return Optional.empty(); + } + + private static void jsonResponse(Context c){ + jsonResponse(c, false); + } + + + private static void jsonResponse(Context c, Boolean compress) { + if(retryTimes > 0){ + if(retrySeconds != null) { + c.header("Retry-After", retrySeconds); + } + retryTimes--; + c.status(retryStatus); + return; + } + + String content = simpleResponse(c) + .orElseGet(() -> { + RequestCapture value = getRequestCapture(c); + return om.writeValue(value); + }); + if(compress){ + c.result(zip(content)); + } else { + c.res().setCharacterEncoding(StandardCharsets.UTF_8.name()); + c.result(content); + } + } + + private static byte[] zip(String content) { + try { + ByteArrayOutputStream obj = new ByteArrayOutputStream(); + GZIPOutputStream gzip = new GZIPOutputStream(obj); + gzip.write(content.getBytes("UTF-8")); + gzip.close(); + + return obj.toByteArray(); + }catch (Exception e){ + throw new RuntimeException(e); + } + } + + private static RequestCapture getRequestCapture(Context context) { + RequestCapture value = new RequestCapture(context); + value.writeBody(context); + return value; + } + + public static void shutdown() { + app.stop(); + } + + public static void setStringResponse(String stringResponse) { + MockServer.responseBody = stringResponse; + } + + public static void addResponseHeader(String key, String value) { + responseHeaders.add(new Pair<>(key, value)); + } + + public static void expectedPages(int expected) { + pages = expected; + } + + public static void clearCookies() { + cookies.clear(); + } + + public static void expectCookie(String name, String value) { + cookies.add(new Cookie(name, UrlEncoded.encodeString(value))); + } + + public static void expectCookie(Cookie cookie) { + cookies.add(cookie); + } + + public static void clearHeaders() { + responseHeaders.clear(); + } + + public static RequestCapture lastRequest() { + return lastRequest; + } + + public static class WebSocketHandler implements Consumer { + + private static String onOpenMessage = "Open"; + private WsConfig handler; + public static Map headers = new HashMap<>(); + + public static void reset() { + headers.clear(); + onOpenMessage = "Open"; + } + + public static void expectOpeningMessage(String message) { + onOpenMessage = message; + } + + @Override + public void accept(WsConfig wsHandler) { + this.handler = wsHandler; + this.handler.onMessage(c -> { + c.send("thank you"); + }); + this.handler.onConnect(c -> { + headers = c.headerMap(); + c.send(onOpenMessage); + }); + } + } + + public static void retryTimes(int numberOfTimeToFail, int status, Double seconds){ + retryTimes(numberOfTimeToFail, status, String.valueOf(seconds)); + } + + public static void retryTimes(int numberOfTimeToFail, int status, String seconds) { + retryTimes = numberOfTimeToFail; + retryStatus = status; + retrySeconds = seconds; + } + + public static void assertRequestCount(int i) { + assertEquals(i, timesCalled); + } + + public static void expectErrorCode(int i) { + errorCode = i; + } + + public static final class Sse { + public static void sendComment(String comment) { + TestSSEConsumer.sendComment(comment); + } + + public static void sendEvent(String content) { + TestSSEConsumer.sendEvent(content); + } + + public static void sendEvent(String id, String event, String content) { + TestSSEConsumer.sendEvent(id, event, content); + } + + public static void sendEvent(String event, String content) { + TestSSEConsumer.sendEvent(event, content); + } + + public static void queueEvent(String id, String event, String content) { + TestSSEConsumer.queueEvent(id, event, content); + } + + public static RequestCapture lastRequest() { + return TestSSEConsumer.getLastRequest(); + } + + public static void keepAlive(boolean value) { + TestSSEConsumer.keepAlive(value); + } + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/MultiPartFormPostingTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/MultiPartFormPostingTest.java new file mode 100644 index 000000000..bbb607041 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/MultiPartFormPostingTest.java @@ -0,0 +1,610 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.*; +import org.junit.jupiter.api.Test; + +import java.io.*; +import java.net.URISyntaxException; + +import static BehaviorTests.TestUtil.*; +import static java.util.Arrays.asList; +import static kong.unirest.core.ContentType.APPLICATION_JSON; +import static kong.unirest.core.ContentType.APPLICATION_PDF; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class MultiPartFormPostingTest extends BddTest { + + @Test + void testMultipart() { + Unirest.post(MockServer.POST) + .field("name", "Mark") + .field("funky", "bunch") + .field("file", rezFile("/test.txt")) + .asObject(RequestCapture.class) + .getBody() + .assertParam("name", "Mark") + .assertMultiPartContentType() + .getFile("test.txt") + .assertBody("This is a test file") + .assertFileType("application/octet-stream"); + } + + @Test + void fileSizeDoesntChange() throws Exception { + var file = rezFile("/image.jpg"); + var size = file.length(); + + RequestCapture capture = Unirest.post(MockServer.POST) + .field("file", file) + .field("file2", new FileInputStream(file), "file2.jpg") + .asObject(RequestCapture.class) + .getBody() + .assertMultiPartContentType(); + + capture.getFile("image.jpg").assertSize(size); + capture.getFile("file2.jpg").assertSize(size); + } + + @Test + void testMultipartContentType() { + Unirest.post(MockServer.POST) + .field("name", "Mark") + .field("file", rezFile("/image.jpg"), "image/jpeg") + .asObject(RequestCapture.class) + .getBody() + .assertMultiPartContentType() + .assertParam("name", "Mark") + .getFile("image.jpg") + .assertFileType("image/jpeg"); + } + + @Test + void canSendRawInputStreamsWithoutAFileName() throws Exception { + var stream = new FileInputStream(rezFile("/test.txt")); + + Unirest.post(MockServer.POST) + .field("file", stream) + .asObject(RequestCapture.class) + .getBody() + .assertParam("file", "This is a test file"); + } + + @Test + void multipleFiles_sameField() { + var body = Unirest.post(MockServer.POST) + .field("file", rezFile("/image.jpg")) + .field("file", rezFile("/spidey.jpg")) + .asObject(RequestCapture.class) + .getBody(); + + body.getFile("image.jpg").exists(); + body.getFile("spidey.jpg").exists(); + assertEquals(2, body.getAllFilesByInput("file").size()); + } + + @Test + void testMultipartInputStreamContentType() throws Exception { + FileInputStream stream = new FileInputStream(rezFile("/image.jpg")); + + Unirest.post(MockServer.POST) + .header("accept", ContentType.MULTIPART_FORM_DATA.toString()) + .field("name", "Mark") + .field("file", stream, ContentType.APPLICATION_OCTET_STREAM, "image.jpg") + .asObject(RequestCapture.class) + .getBody() + .assertMultiPartContentType() + .assertHeader("Accept", "multipart/form-data") + .assertParam("name", "Mark") + .getFile("image.jpg") + .assertFileType("application/octet-stream"); + } + + @Test + void testMultipartInputStreamContentTypeAsync() throws Exception { + Unirest.post(MockServer.POST) + .field("name", "Mark") + .field("file", new FileInputStream(rezFile("/test.txt")), ContentType.APPLICATION_OCTET_STREAM, "test.txt") + .asJsonAsync(new MockCallback<>(this, r -> parse(r) + .assertParam("name", "Mark") + .assertMultiPartContentType() + .getFile("test.txt") + .assertFileType("application/octet-stream")) + ); + + assertAsync(); + } + + @Test + void testMultipartByteContentType() throws Exception { + var bytes = getFileBytes("/image.jpg"); + + Unirest.post(MockServer.POST) + .field("boot", "boot") + .field("file", bytes, "image.jpg") + .asObject(RequestCapture.class) + .getBody() + .assertMultiPartContentType() + .getFile("image.jpg") + .assertFileType("application/octet-stream"); + } + + @Test + void testMultipartByteContentTypeAsync() throws Exception { + var bytes = getFileBytes("/test.txt"); + + Unirest.post(MockServer.POST) + .field("name", "Mark") + .field("file", bytes, "test.txt") + .asJsonAsync(new MockCallback<>(this, r -> + parse(r) + .assertParam("name", "Mark") + .assertMultiPartContentType() + .getFile("test.txt") + .assertFileType("application/octet-stream")) + ); + + assertAsync(); + } + + + @Test + void testMultipartAsync() { + Unirest.post(MockServer.POST) + .field("name", "Mark") + .field("file", rezFile("/test.txt")) + .asJsonAsync(new MockCallback<>(this, r -> + parse(r) + .assertParam("name", "Mark") + .assertMultiPartContentType() + .getFile("test.txt") + .assertFileType("application/octet-stream") + .assertBody("This is a test file")) + ); + + assertAsync(); + } + + @Test + void utf8FileNames() { + var fileData = new ByteArrayInputStream(new byte[]{'t', 'e', 's', 't'}); + var filename = "fileĂ€Ă¶ĂŒ.pöf"; + + Unirest.post(MockServer.POST) + .field("file", fileData, filename) + .asObject(RequestCapture.class) + .getBody() + .assertMultiPartContentType() + .getFile(filename) + .assertFileName(filename); + } + + @Test + void simpleMultiPart() { + Unirest.post(MockServer.POST) + .field("foo", "bar") + .field("fruit", "apple") + .asObject(RequestCapture.class) + .getBody() + .assertParam("foo", "bar"); + } + + @Test + void canSetModeToStrictForLegacySupport() { + var fileData = new ByteArrayInputStream(new byte[]{'t', 'e', 's', 't'}); + var filename = "fileĂ€Ă¶ĂŒ.pöf"; + + Unirest.post(MockServer.POST) + .field("file", fileData, filename) + .mode(MultipartMode.STRICT) + .asObject(RequestCapture.class) + .getBody() + .assertMultiPartContentType() + .getFile("file???.p?f") + .assertFileName("file???.p?f"); + } + + @Test + void canPostInputStreamWithContentType() throws Exception { + var file = TestUtil.getImageFile(); + Unirest.post(MockServer.POST) + .field("testfile", new FileInputStream(file), ContentType.IMAGE_JPEG, "image.jpg") + .asObject(RequestCapture.class) + .getBody() + .assertMultiPartContentType() + .getFileByInput("testfile") + .assertFileName("image.jpg") + .assertFileType("image/jpeg"); + } + + @Test + void canPostInputStream() throws Exception { + var file = TestUtil.getImageFile(); + Unirest.post(MockServer.POST) + .field("testfile", new FileInputStream(file), "image.jpg") + .asObject(RequestCapture.class) + .getBody() + .assertMultiPartContentType() + .getFileByInput("testfile") + .assertFileName("image.jpg") + .assertFileType("application/octet-stream"); + } + + @Test + void postFieldsAsMap() throws URISyntaxException { + var file = TestUtil.getImageFile(); + + Unirest.post(MockServer.POST) + .fields(TestUtil.mapOf("big", "bird", "charlie", 42, "testfile", file, "gonzo", null)) + .asObject(RequestCapture.class) + .getBody() + .assertParam("big", "bird") + .assertParam("charlie", "42") + .assertParam("gonzo", "") + .assertMultiPartContentType() + .getFile("image.jpg") + .assertFileType("application/octet-stream"); + } + + @Test + void postFileWithoutContentType() { + var file = TestUtil.getImageFile(); + Unirest.post(MockServer.POST) + .field("testfile", file) + .asObject(RequestCapture.class) + .getBody() + .assertMultiPartContentType() + .getFile("image.jpg") + .assertFileType("application/octet-stream"); + } + + @Test + void postFileWithContentType() { + var file = TestUtil.getImageFile(); + Unirest.post(MockServer.POST) + .field("testfile", file, ContentType.IMAGE_JPEG.getMimeType()) + .asObject(RequestCapture.class) + .getBody() + .assertMultiPartContentType() + .getFile("image.jpg") + .assertFileType(ContentType.IMAGE_JPEG); + } + + @Test + void multiPartInputStreamAsFile() throws FileNotFoundException { + Unirest.post(MockServer.POST) + .field("foo", "bar") + .field("filecontents", new FileInputStream(rezFile("/image.jpg")), "image.jpg") + .asObject(RequestCapture.class) + .getBody() + .assertMultiPartContentType() + .assertParam("foo", "bar") + .getFileByInput("filecontents") + .assertFileType(ContentType.APPLICATION_OCTET_STREAM) + .assertFileName("image.jpg"); + } + + @Test + void testPostMulipleFIles() { + var cap = Unirest.post(MockServer.POST) + .field("name", asList(rezFile("/test.txt"), rezFile("/test2.txt"))) + .asObject(RequestCapture.class) + .getBody() + .assertMultiPartContentType(); + + cap.getFile("test.txt").assertBody("This is a test file"); + cap.getFile("test2.txt").assertBody("this is another test"); + } + + @Test + void testPostMultipleFiles() throws Exception { + Unirest.post(MockServer.POST) + .field("param3", "wot") + .field("file1", rezFile("/test.txt")) + .field("file2", rezFile("/test.txt")) + .asObject(RequestCapture.class) + .getBody() + .assertMultiPartContentType() + .assertParam("param3", "wot") + .assertFileContent("file1", "This is a test file") + .assertFileContent("file2", "This is a test file"); + } + + @Test + void testPostBinaryUTF8() throws Exception { + Unirest.post(MockServer.POST) + .header("Accept", ContentType.MULTIPART_FORM_DATA.getMimeType()) + .field("param3", "こんにづは") + .field("file", rezFile("/test.txt")) + .asObject(RequestCapture.class) + .getBody() + .assertMultiPartContentType() + .assertParam("param3", "こんにづは") + .assertFileContent("file", "This is a test file"); + } + + @Test + void testMultipeInputStreams() throws FileNotFoundException { + Unirest.post(MockServer.POST) + .field("name", asList(new FileInputStream(rezFile("/test.txt")), new FileInputStream(rezFile("/test2.txt")))) + .asObject(RequestCapture.class) + .getBody() + .assertMultiPartContentType() + .assertParam("name", "This is a test file") + .assertParam("name", "this is another test"); + } + + @Test + void multiPartInputStream() throws FileNotFoundException { + Unirest.post(MockServer.POST) + .field("foo", "bar") + .field("filecontents", new FileInputStream(rezFile("/test.txt")), ContentType.WILDCARD) + .asObject(RequestCapture.class) + .getBody() + .assertMultiPartContentType() + .assertParam("foo", "bar") + .assertParam("filecontents", "This is a test file"); + } + + @Test + void passFileAsByteArray() { + Unirest.post(MockServer.POST) + .field("foo", "bar") + .field("filecontents", TestUtil.getFileBytes("/image.jpg"), ContentType.IMAGE_JPEG, "image.jpg") + .asObject(RequestCapture.class) + .getBody() + .assertMultiPartContentType() + .assertParam("foo", "bar") + .getFileByInput("filecontents") + .assertFileType(ContentType.IMAGE_JPEG) + .assertFileName("image.jpg"); + } + + @Test + void nullFileResultsInEmptyPost() { + Unirest.post(MockServer.POST) + .field("testfile", (Object) null, ContentType.IMAGE_JPEG.getMimeType()) + .asObject(RequestCapture.class) + .getBody() + .assertParam("testfile", ""); + } + + @Test + void canForceIntoMultiPart() { + Unirest.post(MockServer.POST) + .multiPartContent() + .field("foo", "bar") + .asObject(RequestCapture.class) + .getBody() + .assertMultiPartContentType() + .assertParam("foo", "bar"); + } + + @Test + void rawInspection() { + var body = Unirest.post(MockServer.ECHO_RAW) + .field("marky", "mark") + .field("funky", "bunch") + .field("file", rezFile("/test.txt")) + .asString() + .getBody(); + + var expected = TestUtil.getResource("rawPost.txt").replaceAll("\r", "").trim(); + + var id = body.substring(2, body.indexOf("\n") - 1); + body = body.replaceAll(id, "IDENTIFIER").replaceAll("\r", "").trim(); + + assertEquals(expected, body); + } + + @Test + void rawInspectionSorted() { + var body = Unirest.post(MockServer.ECHO_RAW) + .field("marky", "mark") + .field("funky", "bunch") + .field("file", rezFile("/test.txt")) + .sortFields() + .asString() + .getBody(); + + var expected = TestUtil.getResource("rawPostSorted.txt").replaceAll("\r", "").trim(); + + var id = body.substring(2, body.indexOf("\n") - 1); + body = body.replaceAll(id, "IDENTIFIER").replaceAll("\r", "").trim(); + + assertEquals(expected, body); + } + + @Test + void mediaTypesForParts() { + Unirest.post(MockServer.POST) + .field("content", rezInput("/spidey.pdf"), APPLICATION_PDF, "spiderman") + .field("metadata", "{\"foo\": 1}", APPLICATION_JSON) + .asObject(RequestCapture.class) + .getBody() + .assertHeader("Content-Type", h -> { + h.assertMainValue("multipart/form-data"); + h.assertHasParam("boundary"); + h.assertParam("charset", "UTF-8"); + }) + .assertBodyPart("content", p -> { + p.assertFileName("spiderman"); + p.assertContentType("application/pdf"); + p.assertContentDisposition("form-data; name=\"content\"; filename=\"spiderman\""); + }) + .assertBodyPart("metadata", p -> { + p.assertBody("{\"foo\": 1}"); + p.assertContentType("application/json"); + p.assertContentDisposition("form-data; name=\"metadata\""); + }); + } + + @Test + void addBodyPartWithBuilder() { + Unirest.post(MockServer.POST) + .field(MultiPartBuilder.named("metadata") + .value("{\"foo\": 1}") + .contentType("application/json") + .header("foo", "bar")) + .asObject(RequestCapture.class) + .getBody() + .assertBodyPart("metadata", p -> { + p.assertBody("{\"foo\": 1}"); + p.assertContentType("application/json"); + p.assertContentDisposition("form-data; name=\"metadata\""); + p.assertHeader("foo", "bar"); + }); + } + + @Test + void partBuilderWithFiles() { + Unirest.post(MockServer.POST) + .field(MultiPartBuilder.named("content") + .value(rezInput("/spidey.pdf")) + .fileName("spiderman") + .header("foo", "bar")) + .asObject(RequestCapture.class) + .getBody() + .assertHeader("Content-Type", h -> { + h.assertMainValue("multipart/form-data"); + h.assertHasParam("boundary"); + h.assertParam("charset", "UTF-8"); + }) + .assertBodyPart("content", p -> { + p.assertFileName("spiderman"); + p.assertContentType("application/octet-stream"); + p.assertContentDisposition("form-data; name=\"content\"; filename=\"spiderman\""); + p.assertHeader("foo", "bar"); + }); + } + + @Test + void partBuilderWithInputStream() throws Exception { + Unirest.post(MockServer.POST) + .field(MultiPartBuilder.named("content") + .value(new FileInputStream(rezFile("/test.txt"))) + .fileName("testfile") + .contentType(ContentType.WILDCARD) + .header("foo", "bar")) + .asObject(RequestCapture.class) + .getBody() + .assertHeader("Content-Type", h -> { + h.assertMainValue("multipart/form-data"); + h.assertHasParam("boundary"); + h.assertParam("charset", "UTF-8"); + }) + .assertBodyPart("content", p -> { + p.assertFileName("testfile"); + p.assertContentType(ContentType.WILDCARD.getMimeType()); + p.assertContentDisposition("form-data; name=\"content\"; filename=\"testfile\""); + p.assertHeader("foo", "bar"); + }); + } + + @Test + void partBuilderWithBytes() throws Exception { + Unirest.post(MockServer.POST) + .field(MultiPartBuilder.named("content") + .value(getFileBytes("/image.jpg")) + .fileName("testfile") + .contentType(ContentType.WILDCARD) + .header("foo", "bar")) + .asObject(RequestCapture.class) + .getBody() + .assertHeader("Content-Type", h -> { + h.assertMainValue("multipart/form-data"); + h.assertHasParam("boundary"); + h.assertParam("charset", "UTF-8"); + }) + .assertBodyPart("content", p -> { + p.assertFileName("testfile"); + p.assertContentType(ContentType.WILDCARD.getMimeType()); + p.assertContentDisposition("form-data; name=\"content\"; filename=\"testfile\""); + p.assertHeader("foo", "bar"); + }); + } + + @Test + void defaultMediaTypes() { + Unirest.post(MockServer.POST) + .field("content", rezInput("/spidey.pdf"), "spiderman") + .field("metadata", "{\"foo\": 1}") + .asObject(RequestCapture.class) + .getBody() + .assertHeader("Content-Type", h -> { + h.assertMainValue("multipart/form-data"); + h.assertHasParam("boundary"); + h.assertParam("charset", "UTF-8"); + }) + .assertBodyPart("content", p -> { + p.assertFileName("spiderman"); + p.assertContentType("application/octet-stream"); + p.assertContentDisposition("form-data; name=\"content\"; filename=\"spiderman\""); + }) + .assertBodyPart("metadata", p -> { + p.assertBody("{\"foo\": 1}"); + p.assertContentType("application/x-www-form-urlencoded; charset=UTF-8"); + p.assertContentDisposition("form-data; name=\"metadata\""); + }); + + } + + @Test + void settingTheBoundary() { + String boundary = "ABC-123-BOUNDARY"; + + Unirest.post(MockServer.POST) + .field("spidey", rezFile("/spidey.pdf")) + .field("something", "else") + .boundary(boundary) + .asObject(RequestCapture.class) + .getBody() + .assertHeader("Content-Type", h -> { + h.assertParam("boundary", boundary); + }); + + } + + @Test + void overridingForcingMultiTypeOverridesHeader(){ + Unirest.config() + .setDefaultHeader("Accept", "application/json") + .setDefaultHeader("Content-Type", "application/json") + .setDefaultHeader("Authorization", "Bearer xxx-abc-123"); + + Unirest.post(MockServer.POST) + .basicAuth("username", "password") + .multiPartContent() + .field("username", "xxx") + .field("token", "abc") + .field("grant_type", "password") + .asObject(RequestCapture.class) + .getBody() + .assertHeaderSize("Content-Type", 1) + .assertMultiPartContentType(); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/MyMetric.java b/unirest-bdd-tests/src/test/java/BehaviorTests/MyMetric.java new file mode 100644 index 000000000..dcfba531a --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/MyMetric.java @@ -0,0 +1,77 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import com.google.common.collect.ArrayListMultimap; +import kong.unirest.core.HttpRequestSummary; +import kong.unirest.core.HttpResponseSummary; +import kong.unirest.core.MetricContext; +import kong.unirest.core.UniMetric; + +import java.util.function.Function; + +public class MyMetric implements UniMetric { + private final Function keyFunction; + public ArrayListMultimap routes = ArrayListMultimap.create(); + + public MyMetric(Function keyFunction){ + this.keyFunction = keyFunction; + } + + @Override + public MetricContext begin(HttpRequestSummary request) { + long startNanos = System.nanoTime(); + return (r, e) -> { + routes.put(keyFunction.apply(request), new Execution(System.nanoTime() - startNanos, statusOr(r), e)); + }; + } + + private int statusOr(HttpResponseSummary r) { + if(r == null){ + return -1; + } + return r.getStatus(); + } + + public long countResponses(int i) { + return routes.entries() + .stream() + .filter(e -> e.getValue().status == i) + .count(); + } + + public static class Execution { + public final long time; + public final int status; + public final Exception e; + + public Execution(long time, int status, Exception e){ + this.time = time; + this.status = status; + this.e = e; + } + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/NoopCallback.java b/unirest-bdd-tests/src/test/java/BehaviorTests/NoopCallback.java new file mode 100644 index 000000000..32afe5843 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/NoopCallback.java @@ -0,0 +1,47 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.Callback; +import kong.unirest.core.HttpResponse; +import kong.unirest.core.UnirestException; + +public class NoopCallback implements Callback { + @Override + public void completed(HttpResponse response) { + + } + + @Override + public void failed(UnirestException e) { + + } + + @Override + public void cancelled() { + + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/ObjectFunctionalTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/ObjectFunctionalTest.java new file mode 100644 index 000000000..7dd4ee141 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/ObjectFunctionalTest.java @@ -0,0 +1,100 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import com.google.gson.Gson; +import kong.unirest.core.HttpResponse; +import org.junit.jupiter.api.Test; +import kong.unirest.core.Unirest; + +import java.util.HashMap; +import java.util.Map; + +import static com.google.common.collect.ImmutableMap.of; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ObjectFunctionalTest extends BddTest { + + private final Gson gson = new Gson(); + + @Test + void canUseAFunctionToTransform() { + MockServer.setJsonAsResponse(of("foo", "bar")); + + var r = Unirest.get(MockServer.GET) + .asObject(i -> gson.fromJson(i.getContentReader(), HashMap.class)) + .getBody(); + + assertEquals("bar", r.get("foo")); + } + + @Test + void canUseAFunctionToTransformAsync() throws Exception { + MockServer.setJsonAsResponse(of("foo", "bar")); + + var r = Unirest.get(MockServer.GET) + .asObjectAsync(i -> gson.fromJson(i.getContentReader(), HashMap.class)) + .get() + .getBody(); + + assertEquals("bar", r.get("foo")); + } + + @Test + void willNotStopForParsingExceptions() { + MockServer.setStringResponse("call me ishmael"); + + var ohNoes = new RuntimeException("oh noes"); + + HttpResponse r = Unirest.get(MockServer.GET) + .asObject(i -> { + throw ohNoes; + }); + + assertEquals(200, r.getStatus()); + assertTrue(r.getParsingError().isPresent()); + assertEquals(ohNoes, r.getParsingError().get().getCause()); + assertEquals("call me ishmael", r.getParsingError().get().getOriginalBody()); + } + + @Test + void willNotStopForParsingExceptions_async() throws Exception { + MockServer.setStringResponse("call me ishmael"); + + var ohNoes = new RuntimeException("oh noes"); + + var r = Unirest.get(MockServer.GET) + .asObjectAsync(i -> { + throw ohNoes; + }).get(); + + assertEquals(200, r.getStatus()); + assertTrue(r.getParsingError().isPresent()); + assertEquals(ohNoes, r.getParsingError().get().getCause()); + assertEquals("call me ishmael", r.getParsingError().get().getOriginalBody()); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/Pair.java b/unirest-bdd-tests/src/test/java/BehaviorTests/Pair.java new file mode 100644 index 000000000..103d049a6 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/Pair.java @@ -0,0 +1,37 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +public class Pair { + public final K key; + public final V value; + + public Pair(K key, V value){ + + this.key = key; + this.value = value; + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/PathParamTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/PathParamTest.java new file mode 100644 index 000000000..e4dd7e37b --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/PathParamTest.java @@ -0,0 +1,192 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import com.google.common.collect.ImmutableMap; +import kong.unirest.core.HttpMethod; +import kong.unirest.core.HttpRequestSummary; +import kong.unirest.core.Unirest; +import kong.unirest.core.UnirestException; +import org.junit.jupiter.api.Test; + +import java.net.URLEncoder; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class PathParamTest extends BddTest { + + @Test + void canAddRouteParamsAsMap() { + var param = "Hamberders"; + + Unirest.get(MockServer.PASSED_PATH_PARAM_MULTI) + .routeParam(ImmutableMap.of("params", param, "another", 42)) + .asObject(RequestCapture.class) + .getBody() + .assertUrl("http://localhost:4567/get/Hamberders/passed/42") + .assertPathParam("params", param) + .assertPathParam("another", "42"); + } + + @Test + void specialCharactersInTheDirectoryPath() { + Unirest.get(MockServer.GET + "/" + URLEncoder.encode("[brackets]") + "/passed") + .asObject(RequestCapture.class) + .getBody() + .assertUrl("http://localhost:4567/get/%5Bbrackets%5D/passed"); + + Unirest.get(MockServer.PASSED_PATH_PARAM) + .routeParam("params", "[brackets]") + .asObject(RequestCapture.class) + .getBody() + .assertUrl("http://localhost:4567/get/%5Bbrackets%5D/passed"); + } + + @Test + void properlyDealsWithPlusInPAth() { + var param = "jack+4@email.com"; + + Unirest.get(MockServer.PASSED_PATH_PARAM) + .routeParam("params", param) + .asObject(RequestCapture.class) + .getBody() + .assertUrl("http://localhost:4567/get/jack%2B4%40email.com/passed") + .assertPathParam("params", param); + } + + @Test + void testPathParameters() { + Unirest.get(MockServer.HOST + "/{method}") + .routeParam("method", "get") + .queryString("name", "Mark") + .asObject(RequestCapture.class) + .getBody() + .assertParam("name", "Mark"); + } + + @Test + void testQueryAndBodyParameters() { + Unirest.post(MockServer.HOST + "/{method}") + .routeParam("method", "post") + .queryString("name", "Mark") + .field("wot", "wat") + .asObject(RequestCapture.class) + .getBody() + .assertParam("name", "Mark") + .assertParam("wot", "wat"); + } + + @Test + void testPathParameters2() { + Unirest.patch(MockServer.HOST + "/{method}") + .routeParam("method", "patch") + .field("name", "Mark") + .asObject(RequestCapture.class) + .getBody() + .assertParam("name", "Mark"); + } + + @Test + void testMissingPathParameter() { + var ex = assertThrows(UnirestException.class, () -> + Unirest.get(MockServer.HOST + "/{method}") + .routeParam("method222", "get") + .queryString("name", "Mark") + .asEmpty()); + assertEquals("Can't find route parameter name \"method222\"", ex.getMessage()); + } + + @Test + void testMissingPathParameterValue() { + var ex = assertThrows(UnirestException.class, () -> + Unirest.get(MockServer.HOST + "/{method}") + .queryString("name", "Mark") + .asEmpty()); + assertEquals("java.lang.IllegalArgumentException: Illegal character in path at index 22: http://localhost:4567/{method}?name=Mark", ex.getMessage()); + } + + @Test + void illigalPathParams() { + var value = "/?ЊЯЯ"; + + Unirest.get(MockServer.PASSED_PATH_PARAM) + .routeParam("params", value) + .asObject(RequestCapture.class) + .getBody() + .assertUrl("http://localhost:4567/get/%2F%3F%D0%8A%D0%AF%D0%AF/passed") + .assertPathParam("params", value); + } + + @Test + void spacesAndPluses() { + var value = "Hunky Dory+Cheese Wiz"; + + Unirest.get(MockServer.PASSED_PATH_PARAM) + .routeParam("params", value) + .asObject(RequestCapture.class) + .getBody() + .assertUrl("http://localhost:4567/get/Hunky%20Dory%2BCheese%20Wiz/passed") + .assertPathParam("params", value); + } + + @Test + void nulls() { + Unirest.get(MockServer.PASSED_PATH_PARAM) + .routeParam("params", null) + .asObject(RequestCapture.class) + .getBody() + .assertStatus(404) + .assertUrl("http://localhost:4567/get//passed"); + } + + @Test + void getRequestSummaryOnResponse() { + var sum = Unirest.get(MockServer.PASSED_PATH_PARAM) + .routeParam("params", "cheese") + .queryString("fruit", "apples") + .asEmpty() + .getRequestSummary(); + + assertEquals("http://localhost:4567/get/cheese/passed?fruit=apples", sum.getUrl()); + assertEquals("http://localhost:4567/get/{params}/passed", sum.getRawPath()); + assertEquals(HttpMethod.GET, sum.getHttpMethod()); + } + + @Test + void getRequestSummaryOnResponse_async() throws Exception { + var sum = Unirest.get(MockServer.PASSED_PATH_PARAM) + .routeParam("params", "cheese") + .queryString("fruit", "apples") + .asEmptyAsync() + .get() + .getRequestSummary(); + + assertEquals("http://localhost:4567/get/cheese/passed?fruit=apples", sum.getUrl()); + assertEquals("http://localhost:4567/get/{params}/passed", sum.getRawPath()); + assertEquals(HttpMethod.GET, sum.getHttpMethod()); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/PostRequestHandlersTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/PostRequestHandlersTest.java new file mode 100644 index 000000000..aa4a3e1a3 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/PostRequestHandlersTest.java @@ -0,0 +1,116 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import kong.unirest.core.HttpResponse; +import kong.unirest.core.Unirest; + +import java.util.function.Consumer; + +import static org.junit.jupiter.api.Assertions.*; + +class PostRequestHandlersTest extends BddTest { + + private HttpResponse captured; + + @Override + @AfterEach + public void tearDown() { + super.tearDown(); + captured = null; + } + + @Test + void onSuccessDoSomething() { + Unirest.get(MockServer.GET) + .queryString("foo", "bar") + .asObject(RequestCapture.class) + .ifSuccess(r -> captured = r) + .ifFailure(r -> fail("should not have been called")); + + assertNotNull(captured); + captured.getBody().assertParam("foo", "bar"); + } + + @Test + void onFailDoSomething() { + Unirest.get(MockServer.INVALID_REQUEST) + .queryString("foo", "bar") + .asObject(RequestCapture.class) + .ifFailure(r -> captured = r) + .ifSuccess(r -> fail("should not have been called")); + + assertNotNull(captured); + assertEquals(400, captured.getStatus()); + } + + @Test + void itsAFailIfTheMapperFails() { + MockServer.setStringResponse("not what you expect"); + + Unirest.get(MockServer.GET) + .queryString("foo", "bar") + .asObject(RequestCapture.class) + .ifFailure(r -> captured = r) + .ifSuccess(r -> fail("should not have been called")); + + assertNotNull(captured); + assertEquals(200, captured.getStatus()); + assertTrue(captured.getParsingError().isPresent()); + assertEquals("not what you expect", captured.getParsingError().get().getOriginalBody()); + } + + @Test + void onSuccessBeSuccessful() { + var response = Unirest.get(MockServer.GET) + .queryString("foo", "bar") + .asObject(RequestCapture.class); + + assertTrue(response.isSuccess()); + } + + @Test + void onFailBeUnsuccessful() { + var response = Unirest.get(MockServer.INVALID_REQUEST) + .queryString("foo", "bar") + .asObject(RequestCapture.class); + + assertFalse(response.isSuccess()); + } + + @Test + void beUnsuccessfulIfTheMapperFails() { + MockServer.setStringResponse("not what you expect"); + + var response = Unirest.get(MockServer.GET) + .queryString("foo", "bar") + .asObject(RequestCapture.class); + + assertFalse(response.isSuccess()); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/ProxyTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/ProxyTest.java new file mode 100644 index 000000000..f4580b647 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/ProxyTest.java @@ -0,0 +1,183 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.Proxy; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import kong.unirest.core.Unirest; + +import java.io.IOException; +import java.net.*; +import java.util.List; + +import static java.net.Proxy.Type.HTTP; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Disabled // The Janky Proxy is pretty janky and isn't entirely stable in CI +class ProxyTest extends BddTest { + + @AfterEach + @Override + public void tearDown() { + super.tearDown(); + Unirest.shutDown(true); + JankyProxy.shutdown(); + } + + @Test + void canUseNonAuthProxy() { + JankyProxy.runServer("localhost", 4567, 7777); + + Unirest.config().proxy(new Proxy("localhost", 7777)); + + Unirest.get(MockServer.GET) + .asObject(RequestCapture.class) + .getBody() + .assertStatus(200); + + assertTrue(JankyProxy.wasUsed()); + } + + @Test + void canUseNonAuthProxyWithEasyMethod() { + JankyProxy.runServer("localhost", 4567, 7777); + + Unirest.config().proxy("localhost", 7777); + + Unirest.get(MockServer.GET) + .asObject(RequestCapture.class) + .getBody() + .assertStatus(200); + + assertTrue(JankyProxy.wasUsed()); + } + + @Test + void canUseSelector() { + JankyProxy.runServer("localhost", 4567, 7777); + + Unirest.config().proxy(new ProxySelector() { + @Override + public List select(URI uri) { + var address = InetSocketAddress.createUnresolved("localhost", 7777); + return List.of(new java.net.Proxy(HTTP, address)); + } + + @Override + public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { + throw new RuntimeException(ioe); + } + }); + + Unirest.get(MockServer.GET) + .asObject(RequestCapture.class) + .getBody() + .assertStatus(200); + + assertTrue(JankyProxy.wasUsed()); + } + + @Test + void canSetAuthenticatedProxy() { + JankyProxy.runServer("localhost", 4567, 7777); + + Unirest.config().proxy("localhost", 7777, "username", "password1!"); + + Unirest.get(MockServer.GET) + .asObject(RequestCapture.class) + .getBody() + .assertStatus(200); + + assertTrue(JankyProxy.wasUsed()); + } + + @Test + @Disabled + // there is some weird conflict between jetty and unirest here + void canFlagTheClientsToUseSystemProperties() { + JankyProxy.runServer("localhost", 4567, 7777); + + System.setProperty("http.proxyHost", "localhost"); + System.setProperty("http.proxyPort", "7777"); + + Unirest.config().useSystemProperties(true); + + Unirest.get(MockServer.GET) + .asObject(RequestCapture.class) + .getBody() + .assertStatus(200); + + assertTrue(JankyProxy.wasUsed()); + } + + @Test + void multipleOfEverything() { + JankyProxy.runServer("localhost", 4567, 7777); + + Unirest.config() + .proxy(new ProxySelector() { + @Override + public List select(URI uri) { + if (uri.getHost().equals("homestarrunner.com")) { + return List.of(new java.net.Proxy(HTTP, InetSocketAddress.createUnresolved("proxy-sad.com", 7777))); + } + + return List.of(new java.net.Proxy(HTTP, InetSocketAddress.createUnresolved("default.com", 7777))); + } + + @Override + public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { + + } + }) + + .authenticator(new Authenticator() { + @Override + public PasswordAuthentication requestPasswordAuthenticationInstance(String host, InetAddress addr, + int port, String protocol, + String prompt, String scheme, + URL url, RequestorType reqType) { + // Please don't hardcode passwords in your code :D + if(host.equals("homestarrunner.com")) { + return new PasswordAuthentication("strongbad", "password".toCharArray()); + } + return new PasswordAuthentication("default", "password".toCharArray()); + } + }); + + } + + // @Test @Disabled // https://free-proxy-list.net/ +// void callSomethingRealThroughARealProxy() { +// Unirest.config().proxy("18.222.230.116",8080); +// //Unirest.config().proxy("34.73.62.46",3128, "myuser","pass1!"); +// HttpResponse r = Unirest.get("https://twitter.com/ryber").asString(); +// System.out.println("status = " + r.getStatus()); +// System.out.println("body= " + r.getBody()); +// } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/QueryMethodTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/QueryMethodTest.java new file mode 100644 index 000000000..9f7dce53d --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/QueryMethodTest.java @@ -0,0 +1,53 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; + +public class QueryMethodTest extends BddTest { + @Test + void canUseQueryVerbWithFormFields() { + Unirest.query(MockServer.QUERY) + .queryString("foo", "bar") + .field("fruit", "apple") + .asObject(RequestCapture.class) + .getBody() + .assertParam("foo", "bar") + .assertParam("fruit", "apple"); + } + + @Test + void canUseQueryVerbWithUniBody() { + Unirest.query(MockServer.QUERY) + .queryString("foo", "bar") + .body("this is a very complicated query") + .asObject(RequestCapture.class) + .getBody() + .assertParam("foo", "bar") + .assertBody("this is a very complicated query"); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/QueryStringTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/QueryStringTest.java new file mode 100644 index 000000000..408a15979 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/QueryStringTest.java @@ -0,0 +1,132 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.HttpResponse; +import kong.unirest.core.JsonNode; +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; + +class QueryStringTest extends BddTest { + + @Test + void testGetQueryStrings() { + Unirest.get(MockServer.GET) + .queryString("name", "mark") + .queryString("nick", "thefosk") + .asObject(RequestCapture.class) + .getBody() + .assertParam("name", "mark") + .assertParam("nick", "thefosk"); + } + + @Test + void canPassQueryParamsDirectlyOnUriOrWithMethod() { + Unirest.get(MockServer.GET + "?name=mark") + .asObject(RequestCapture.class) + .getBody() + .assertParam("name", "mark"); + + Unirest.get(MockServer.GET) + .queryString("name", "mark2") + .asObject(RequestCapture.class) + .getBody() + .assertParam("name", "mark2"); + } + + @Test + void canPassInACharSequence() { + Unirest.get(MockServer.GET) + .queryString("foo", new StringBuilder("bar")) + .asObject(RequestCapture.class) + .getBody() + .assertParam("foo", "bar"); + } + + @Test + void multipleParams() { + Unirest.get(MockServer.GET + "?name=ringo") + .queryString("name", "paul") + .queryString("name", "john") + .asObject(RequestCapture.class) + .getBody() + .assertParam("name", "ringo") + .assertParam("name", "paul") + .assertParam("name", "john"); + } + + @Test + void testGetUTF8() { + Unirest.get(MockServer.GET) + .queryString("param3", "こんにづは") + .asObject(RequestCapture.class) + .getBody() + .assertParam("param3", "こんにづは"); + } + + @Test + void testGetMultiple() { + for (int i = 1; i <= 20; i++) { + var response = Unirest.get(MockServer.GET + "?try=" + i).asJson(); + parse(response).assertParam("try", String.valueOf(i)); + } + } + + @Test + void testQueryStringEncoding() { + var testKey = "email2=someKey&email"; + var testValue = "hello@hello.com"; + + Unirest.get(MockServer.GET) + .queryString(testKey, testValue) + .asObject(RequestCapture.class) + .getBody() + .assertParam(testKey, testValue); + } + + @Test + void testGetQuerystringArray() { + Unirest.get(MockServer.GET) + .queryString("name", "Mark") + .queryString("name", "Tom") + .asObject(RequestCapture.class) + .getBody() + .assertParam("name", "Mark") + .assertParam("name", "Tom"); + } + + @Test + void testGetArray() { + Unirest.get(MockServer.GET) + .queryString("name", Arrays.asList("Mark", "Tom")) + .asObject(RequestCapture.class) + .getBody() + .assertParam("name", "Mark") + .assertParam("name", "Tom"); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/RawUrlTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/RawUrlTest.java new file mode 100644 index 000000000..6af7e51e3 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/RawUrlTest.java @@ -0,0 +1,58 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; + +class RawUrlTest extends BddTest { + + @Test + void canCallRawPathWithSpace() { + Unirest.get(MockServer.GET + "/foo/passed/Moody Blues") + .asObject(RequestCapture.class) + .getBody() + .assertUrl("http://localhost:4567/get/foo/passed/Moody%20Blues"); + } + + @Test + void canCallRawPathWithTab() { + Unirest.get(MockServer.GET + "/foo/passed/Moody\tBlues") + .asObject(RequestCapture.class) + .getBody() + .assertUrl("http://localhost:4567/get/foo/passed/Moody%09Blues"); + } + + @Test + void doesNotImpactPathParams() { + Unirest.get(MockServer.GET + "/{first param}/passed/{file name}") + .routeParam("first param", "foo") + .routeParam("file name", "Moody Blues") + .asObject(RequestCapture.class) + .getBody() + .assertUrl("http://localhost:4567/get/foo/passed/Moody%20Blues"); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/RedirectHandlingTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/RedirectHandlingTest.java new file mode 100644 index 000000000..a08f8d40d --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/RedirectHandlingTest.java @@ -0,0 +1,79 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.HttpResponse; +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.ExecutionException; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class RedirectHandlingTest extends BddTest { + + @Test + void willFollowRedirectsByDefault() { + Unirest.get(MockServer.REDIRECT) + .asObject(RequestCapture.class) + .getBody() + .assertUrl("http://localhost:4567/get"); + } + + @Test + void redirectOnPost() { + // per the java client a POST will get redirected to a GET + Unirest.post(MockServer.REDIRECT) + .asObject(RequestCapture.class) + .getBody() + .assertUrl("http://localhost:4567/get"); + } + + @Test + void canDisableRedirects(){ + Unirest.config().followRedirects(false); + var response = Unirest.get(MockServer.REDIRECT).asEmpty(); + + assertEquals(301, response.getStatus()); + } + + @Test + void willFollowRedirectsByDefaultAsync() throws ExecutionException, InterruptedException { + Unirest.get(MockServer.REDIRECT) + .asObjectAsync(RequestCapture.class) + .get() + .getBody() + .assertUrl("http://localhost:4567/get"); + } + + @Test + void canDisableRedirectsAsync() throws Exception { + Unirest.config().followRedirects(false); + var response = Unirest.get(MockServer.REDIRECT).asEmptyAsync().get(); + + assertEquals(301, response.getStatus()); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/RequestCapture.java b/unirest-bdd-tests/src/test/java/BehaviorTests/RequestCapture.java new file mode 100644 index 000000000..82a401a2a --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/RequestCapture.java @@ -0,0 +1,405 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.google.common.base.Strings; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.LinkedListMultimap; +import com.google.common.collect.ListMultimap; +import io.javalin.http.Context; +import kong.unirest.core.*; + +import jakarta.servlet.MultipartConfigElement; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.Part; +import org.assertj.core.data.MapEntry; +import org.assertj.guava.api.MultimapAssert; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.util.*; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.lang.System.getProperty; +import static kong.unirest.core.JsonPatchRequest.CONTENT_TYPE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +public class RequestCapture { + public String requestId = UUID.randomUUID().toString(); + public HeaderAsserts headers = new HeaderAsserts(); + public List multiformparts = new ArrayList<>(); + public ArrayListMultimap params = ArrayListMultimap.create(); + public String body; + public String url; + public String queryString; + public HttpMethod method; + public HashMap routeParams = new HashMap<>(); + public String contentType; + public JsonPatch jsonPatches; + public Integer status; + private boolean isProxied; + public HashMap cookies = new HashMap<>(); + + + public RequestCapture(){ + + } + + public RequestCapture(Context req) { + url = req.url(); + queryString = req.queryString(); + method = HttpMethod.valueOf(req.method().name()); + headers = new HeaderAsserts(req); + writeQuery(req); + populateParams(req); + cookies.putAll(req.cookieMap()); + contentType = req.contentType(); + status = 200; + } + + private static String toString(InputStream is) { + return new BufferedReader(new InputStreamReader(is)) + .lines().collect(Collectors.joining("\n")); + } + + private void populateParams(Context req) { + routeParams.putAll(req.pathParamMap()); + } + + public void writeBody(Context req) { + if (Strings.nullToEmpty(req.contentType()).equals(CONTENT_TYPE)) { + String body = req.body(); + jsonPatches = new JsonPatch(body); + this.body = jsonPatches.toString(); + } else { + //parseBodyToFormParams(req); + writeMultipart(req); + } + } + + private void parseBodyToFormParams() { + try { + QueryParams.fromBody(this.body) + .getQueryParams() + .forEach(p -> { + params.put(p.getName(), p.getValue()); + }); + }catch (UnirestException e){} + } + + public void writeMultipart(Context req) { + req.req().setAttribute("org.eclipse.jetty.multipartConfig", new MultipartConfigElement(getProperty("java.io.tmpdir"))); + + try { + for (Part p : req.req().getParts()) { + buildMultiContentPart(p); + } + } catch (ServletException e) { + this.body = req.body(); + parseBodyToFormParams(); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + public void buildMultiContentPart(Part part) throws IOException { + MultiPart file = new MultiPart(); + file.fileName = part.getSubmittedFileName(); + file.type = part.getContentType(); + file.name = part.getName(); + file.fileType = part.getContentType(); + file.size = part.getSize(); + file.body = toString(part.getInputStream()); + file.headers = extractHeaders(part); + if (Strings.isNullOrEmpty(part.getSubmittedFileName())) { + var s = new Scanner(part.getInputStream()).useDelimiter("\\A"); + var value = s.hasNext() ? s.next() : ""; + params.put(part.getName(), value); + } + + multiformparts.add(file); + } + + private ListMultimap extractHeaders(Part part) { + ListMultimap h = LinkedListMultimap.create(); + for (String header : part.getHeaderNames()) { + h.putAll(header.toLowerCase(), part.getHeaders(header)); + } + return h; + } + + private void writeQuery(Context req) { + req.queryParamMap().forEach((key, value) -> params.putAll(key, value)); + } + + public RequestCapture assertNoHeader(String s) { + headers.assertNoHeader(s); + return this; + } + + public RequestCapture assertHeader(String key, String... value) { + headers.assertHeader(key, value); + return this; + } + + public RequestCapture assertParam(String key, String value) { + assertTrue(params.containsKey(key), String.format("Expect param of '%s' but none was present", key)); + assertThat(params.get(key)).contains(value); + return this; + } + + public MultiPart getFile(String fileName) { + return getFileStream() + .filter(f -> Objects.equals(f.fileName, fileName)) + .findFirst() + .orElseThrow(() -> new RuntimeException("\nNo File With Name: " + fileName + "\n" + + "Found: " + getFileStream().map(f -> f.fileName).collect(Collectors.joining(" ")))); + } + + private Stream getFileStream() { + return multiformparts.stream() + .filter(f -> f.isFile()); + } + + public MultiPart getFileByInput(String input) { + return getFileStream() + .filter(f -> Objects.equals(f.name, input)) + .findFirst() + .orElseThrow(() -> new RuntimeException("No File from form: " + input)); + } + + public List getAllFilesByInput(String input) { + return getFileStream() + .filter(f -> Objects.equals(f.name, input)) + .collect(Collectors.toList()); + } + + public RequestCapture assertFileContent(String input, String content) { + assertEquals(content, getFileByInput(input).body); + return this; + } + + public RequestCapture assertBasicAuth(String username, String password) { + headers.assertBasicAuth(username, password); + return this; + } + + public RequestCapture assertQueryString(String s) { + assertEquals(s, queryString); + return this; + } + + public RequestCapture assertMethod(HttpMethod get) { + assertEquals(get, method); + return this; + } + + public RequestCapture assertPathParam(String name, String value) { + assertEquals(value, routeParams.get(name)); + return this; + } + + public RequestCapture assertUrl(String s) { + assertEquals(s, url); + return this; + } + + public void assertCharset(Charset charset) { + assertTrue(contentType.endsWith(charset.toString()), "Expected Content Type With Charset: " + charset); + } + + public RequestCapture assertJsonPatch(JsonPatchOperation op, String path, Object value) { + assertNotNull(jsonPatches, "Asserting JSONPatch but no patch object present"); + assertTrue(jsonPatches.getOperations().contains(new JsonPatchItem(op, path, value))); + return this; + } + + public void setPatch(JsonPatch patch) { + this.jsonPatches = patch; + } + + public RequestCapture assertStatus(Integer i) { + assertEquals(i, status); + return this; + } + + public void setIsProxied(boolean b) { + this.isProxied = b; + } + + public RequestCapture assertIsProxied(boolean b) { + assertEquals(b, isProxied); + return this; + } + + public RequestCapture assertHeaderSize(String name, int size) { + headers.assertHeaderSize(name, size); + return this; + } + + public RequestCapture assertBody(String o) { + assertEquals(o, body); + return this; + } + + public void setStatus(int i) { + this.status = i; + } + + public RequestCapture assertContentType(ContentType content) { + return assertContentType(content.getMimeType()); + } + + public RequestCapture assertContentType(String content) { + return assertHeader("Content-Type", content); + } + + public RequestCapture assertContentType(String content, String paramKey, String paramValue) { + headers.assertHeaderWithParam("Content-Type", content, paramKey, paramValue); + return this; + } + + public RequestCapture assertMultiPartContentType() { + headers.assertMultiPartContentType(); + return this; + } + + public RequestCapture assertUrlEncodedContent() { + return assertRawContentType("application/x-www-form-urlencoded; charset=utf-8"); + } + + public RequestCapture assertCookie(String name, String value) { + String c = cookies.get(name); + assertNotNull(c, "expected a cookie to be passed to the server but got none. Name: " + name); + assertEquals(value, c); + return this; + } + + public RequestCapture assertNoCookie(String name) { + assertNull(cookies.get(name), "Cookie should not have been passed but it was! "); + return this; + } + + public RequestCapture assertRawContentType(String value) { + headers.assertRawValue("Content-Type", value); + return this; + } + + public RequestCapture assertHeader(String name, Consumer validator) { + validator.accept(headers.getFirst(name)); + return this; + } + + public RequestCapture assertBodyPart(String name, Consumer validator) { + Objects.requireNonNull(name); + Objects.requireNonNull(validator); + validator.accept( + multiformparts.stream() + .filter(f -> name.equalsIgnoreCase(f.name)) + .findFirst() + .orElseThrow(() -> new AssertionError("No Form Body Part Found Named: " + name)) + ); + return this; + } + + public RequestCapture assertAccepts(ContentType type) { + return assertHeader("Accept", type.toString()); + } + + public static class MultiPart { + public ListMultimap headers = LinkedListMultimap.create(); + public String content; + public String fileName; + public String type; + public String name; + public String body; + public String fileType; + public long size; + + + @JsonIgnore + public boolean isFile(){ + return fileName != null; + } + + public MultiPart assertBody(String content) { + assertEquals(content, body); + return this; + } + + public MultiPart assertFileType(String type) { + assertEquals(type, this.fileType); + return this; + } + + public MultiPart assertFileType(ContentType imageJpeg) { + return assertFileType(imageJpeg.toString()); + } + + public MultiPart assertFileName(String s) { + assertEquals(s, fileName); + return this; + } + + public MultiPart assertSize(long expected) { + assertEquals(expected, this.size); + return this; + } + + public MultiPart exists() { + assertTrue(this.size > 0); + return this; + } + + public MultiPart assertContentType(String contentType) { + assertHeaders().contains(MapEntry.entry("content-type", contentType)); + return this; + } + + public MultiPart assertContentDisposition(String disposition) { + assertHeaders().contains(MapEntry.entry("content-disposition", disposition)); + return this; + } + + public MultiPart assertHeader(String name, String value) { + assertHeaders().contains(MapEntry.entry(name, value)); + return this; + } + + private MultimapAssert assertHeaders() { + return TestUtil.assertMultiMap(headers); + } + } + +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/ResponseHeaderTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/ResponseHeaderTest.java new file mode 100644 index 000000000..a3fa30bac --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/ResponseHeaderTest.java @@ -0,0 +1,70 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import com.google.common.collect.Lists; +import kong.unirest.core.Headers; +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ResponseHeaderTest extends BddTest { + + @Test + void readingResponseHeaders() { + MockServer.addResponseHeader("zed", "oranges"); + MockServer.addResponseHeader("alpha", "apples"); + MockServer.addResponseHeader("zed", "grapes"); + MockServer.expectCookie("JSESSIONID", "ABC123"); + + Headers h = Unirest.get(MockServer.GET).asString().getHeaders(); + + // assertHeader("Date", "Fri, 04 Jan 2019 01:46:34 GMT", h.all().get(0)); + assertEquals("text/plain;charset=utf-8", h.getFirst("Content-Type")); + assertEquals("JSESSIONID=ABC123; Path=/", h.getFirst("Set-Cookie")); + assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", h.getFirst("Expires")); + assertEquals(Lists.newArrayList("oranges", "grapes"), h.get("zed")); + assertEquals("apples", h.getFirst("alpha")); + } + + @Test + void multipleHeaders() { + MockServer.addResponseHeader("fruit", "oranges"); + MockServer.addResponseHeader("fruit", "apples"); + MockServer.addResponseHeader("fruit", "grapes"); + + var h = Unirest.get(MockServer.GET).asString().getHeaders(); + + assertThat(h.get("fruit")) + .hasSize(3) + .containsExactlyInAnyOrder("oranges","apples","grapes"); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/RetryAsyncTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/RetryAsyncTest.java new file mode 100644 index 000000000..69f610594 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/RetryAsyncTest.java @@ -0,0 +1,174 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.*; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class RetryAsyncTest extends BddTest { + + @Test + void whenSettingIsOff() { + MockServer.retryTimes(100, 429, 1d); + assertEquals(429, Unirest.get(MockServer.GET).asEmpty().getStatus()); + assertEquals(429, Unirest.get(MockServer.GET).asString().getStatus()); + assertEquals(429, Unirest.get(MockServer.GET).asBytes().getStatus()); + assertEquals(429, Unirest.get(MockServer.GET).asObject(RequestCapture.class).getStatus()); + } + + @Test + void dontRetry_whenHeaderIsMissing(){ + MockServer.retryTimes(100, 429, (String)null); + assertDidNotRetry(); + } + + @Test + void dontRetry_whenHeaderIsUnparseable(){ + MockServer.retryTimes(100, 429, "David S Pumpkins"); + assertDidNotRetry(); + } + + @Test + void dontRetry_whenHeaderIsNegative(){ + MockServer.retryTimes(100, 429, -5.5); + assertDidNotRetry(); + } + + @Test + void dontRetry_whenHeaderIsEmpty(){ + MockServer.retryTimes(100, 429, ""); + assertDidNotRetry(); + } + + @Test + void willRetryAfterSeconds_AsObject() { + this.doWithRetry(r -> r.asObjectAsync(RequestCapture.class)) + .assertMethod(HttpMethod.GET); + } + + @Test + void willRetryAfterSeconds_AsObjectGenerics() { + var o = Arrays.asList( + new Foo("foo"), + new Foo("bar"), + new Foo("baz") + ); + MockServer.setJsonAsResponse(o); + var cap = (List)this.doWithRetry(r -> r.asObjectAsync(new GenericType>(){})); + assertEquals(o, cap); + } + + @Test + void willRetryAfterSeconds_AsString() { + MockServer.setStringResponse("Hi Mom"); + var cap = this.doWithRetry(r -> r.asStringAsync()); + assertEquals("Hi Mom", cap); + } + + @Test + void willRetryAfterSeconds_AsJson() { + MockServer.setStringResponse("{\"message\": \"Hi Mom\"}"); + var cap = this.doWithRetry(r -> r.asJsonAsync()); + assertEquals("Hi Mom", cap.getObject().getString("message")); + } + + @Test + void willRetryAfterSeconds_AsBytes() { + MockServer.setStringResponse("Hi Mom"); + var cap = this.doWithRetry(r -> r.asBytesAsync()); + assertEquals("Hi Mom", new String(cap)); + } + + @Test + void willRetryAfterSeconds_Empty() { + doWithRetry(r -> r.asEmptyAsync()); + } + + @Test + void willRetryAfterSeconds_AsFile() { + var path = Paths.get("results.json"); + clearFile(path); + + MockServer.setStringResponse("Hi Mom"); + var cap = (File)this.doWithRetry(r -> r.asFileAsync(path.toString(), StandardCopyOption.REPLACE_EXISTING)); + assertTrue(cap.exists()); + + clearFile(path); + } + + @Test + void willReturn429OnceItExceedsMaxAttempts() { + MockServer.retryTimes(10, 429, .01); + Unirest.config().retryAfter(true, 3); + + var resp = Unirest.get(MockServer.GET).asEmpty(); + assertEquals(429, resp.getStatus()); + MockServer.assertRequestCount(3); + } + + private void clearFile(Path path) { + try { + Files.delete(path); + } catch (Exception ignored) { } + } + + private void assertDidNotRetry() { + Unirest.config().retryAfter(true); + assertEquals(429, Unirest.get(MockServer.GET).asEmpty().getStatus()); + MockServer.assertRequestCount(1); + } + + private R doWithRetry(Function>> bodyExtractor) { + try { + MockServer.retryTimes(1, 429, 0.01); + + Unirest.config().retryAfter(true); + + var request = Unirest.get(MockServer.GET); + + var response = bodyExtractor.apply(request).get(); + assertEquals(200, response.getStatus()); + MockServer.assertRequestCount(2); + + return response.getBody(); + }catch (Exception e){ + throw new AssertionError(e); + } + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/RetryTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/RetryTest.java new file mode 100644 index 000000000..b40ea3fc3 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/RetryTest.java @@ -0,0 +1,213 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +public class RetryTest extends BddTest { + + @Override @BeforeEach + public void setUp() { + super.setUp(); + Unirest.config().retryAfter(true); + } + + @Test + void whenSettingIsOff() { + Unirest.config().retryAfter(false); + + MockServer.retryTimes(100, 429, 1d); + assertEquals(429, Unirest.get(MockServer.GET).asEmpty().getStatus()); + assertEquals(429, Unirest.get(MockServer.GET).asString().getStatus()); + assertEquals(429, Unirest.get(MockServer.GET).asBytes().getStatus()); + assertEquals(429, Unirest.get(MockServer.GET).asObject(RequestCapture.class).getStatus()); + } + + @Test + void dontRetry_whenHeaderIsMissing(){ + MockServer.retryTimes(100, 429, (String)null); + assertDidNotRetry(); + } + + @Test + void dontRetry_whenHeaderIsUnparseable(){ + MockServer.retryTimes(100, 429, "David S Pumpkins"); + assertDidNotRetry(); + } + + @Test + void dontRetry_whenHeaderIsNegative(){ + MockServer.retryTimes(100, 429, -5.5); + assertDidNotRetry(); + } + + @Test + void dontRetry_whenHeaderIsEmpty(){ + MockServer.retryTimes(100, 429, ""); + assertDidNotRetry(); + } + + @Test + void willRetryAfterSeconds_AsObject() { + this.doWithRetry(429, r -> r.asObject(RequestCapture.class)) + .assertMethod(HttpMethod.GET); + } + + @Test + void willRetryAfterSeconds_AsObjectGenerics() { + var o = Arrays.asList( + new Foo("foo"), + new Foo("bar"), + new Foo("baz") + ); + MockServer.setJsonAsResponse(o); + var cap = (List)this.doWithRetry(429, r -> r.asObject(new GenericType>(){})); + assertEquals(o, cap); + } + + @Test + void willRetryAfterSeconds_AsString() { + MockServer.setStringResponse("Hi Mom"); + var cap = this.doWithRetry(429, r -> r.asString()); + assertEquals("Hi Mom", cap); + } + + @Test + void willRetryAfterSeconds_AsJson() { + MockServer.setStringResponse("{\"message\": \"Hi Mom\"}"); + var cap = this.doWithRetry(429, r -> r.asJson()); + assertEquals("Hi Mom", cap.getObject().getString("message")); + } + + @Test + void willRetryAfterSeconds_AsBytes() { + MockServer.setStringResponse("Hi Mom"); + var cap = this.doWithRetry(429, r -> r.asBytes()); + assertEquals("Hi Mom", new String(cap)); + } + + @Test + void willRetryAfterSeconds_Empty() { + doWithRetry(429, r -> r.asEmpty()); + } + + @Test + void willRetryOn503_Empty() { + doWithRetry(503, r -> r.asEmpty()); + } + + @Test + void willRetryAfterSeconds_AsFile() { + var path = Paths.get("results.json"); + clearFile(path); + + MockServer.setStringResponse("Hi Mom"); + var cap = this.doWithRetry(429, r -> r.asFile(path.toString(), StandardCopyOption.REPLACE_EXISTING)); + assertTrue(cap.exists()); + + clearFile(path); + } + + @Test + void willReturn429OnceItExceedsMaxAttempts() { + MockServer.retryTimes(10, 429, .01); + Unirest.config().retryAfter(true, 3); + + var resp = Unirest.get(MockServer.GET).asEmpty(); + assertEquals(429, resp.getStatus()); + MockServer.assertRequestCount(3); + } + + @Test + void canCustomizeRetrySignal() { + MockServer.retryTimes(10, 429, .01); + Unirest.config().retryAfter(true, 3); + + var resp = Unirest.get(MockServer.GET).asEmpty(); + assertEquals(429, resp.getStatus()); + MockServer.assertRequestCount(3); + } + + @Test + void whenBodyIsConsumed() { + MockServer.retryTimes(10, 429, .01); + + var consumer = new ConsumingCounter(); + + Unirest.get(MockServer.GET) + .thenConsume(consumer); + + assertEquals(10, consumer.callCount); + } + + private void clearFile(Path path) { + try { + Files.delete(path); + } catch (Exception ignored) { } + } + + private void assertDidNotRetry() { + Unirest.config().retryAfter(true); + assertEquals(429, Unirest.get(MockServer.GET).asEmpty().getStatus()); + MockServer.assertRequestCount(1); + } + + private R doWithRetry(int status, Function> bodyExtractor) { + MockServer.retryTimes(1, status, 0.01); + + var request = Unirest.get(MockServer.GET); + + var response = bodyExtractor.apply(request); + assertEquals(200, response.getStatus()); + MockServer.assertRequestCount(2); + + return response.getBody(); + } + + private class ConsumingCounter implements Consumer { + int callCount = 0; + @Override + public void accept(RawResponse rawResponse) { + callCount++; + } + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/SSEAsyncTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/SSEAsyncTest.java new file mode 100644 index 000000000..ba5acbc49 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/SSEAsyncTest.java @@ -0,0 +1,156 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + + +import kong.unirest.core.SseRequest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static kong.unirest.core.Unirest.sse; + +@Disabled +public class SSEAsyncTest extends BddTest { + + TestHandler listener; + ExecutorService pool; + + @BeforeEach + public void setUp() { + super.setUp(); + listener = new TestHandler(); + pool = Executors.newFixedThreadPool(1); + } + + @AfterEach + @Override + public void tearDown() { + super.tearDown(); + pool.shutdown(); + } + + @Test + void basicConnection() { + runWith(sse(MockServer.SSE), listener); + + MockServer.Sse.sendComment("hey1"); + MockServer.Sse.sendEvent("Whats Happening?"); + + sleep(1000); + + listener.assertHasEvent("connect", "Welcome to Server Sent Events") + .assertHasComment("hey1") + .assertHasEvent("message", "Whats Happening?"); + } + + @Test + void eventsWithIds() { + runWith(sse(MockServer.SSE), listener); + + MockServer.Sse.sendEvent("123", "cheese", "cheddar"); + MockServer.Sse.sendEvent( "cheese", "gouda"); + + sleep(1000); + + listener.assertHasEvent("123", "cheese", "cheddar") + .assertHasEvent("cheese", "gouda"); + } + + @Test + void queryParams(){ + runWith(sse(MockServer.SSE) + .queryString("foo", "bar") + .queryString(Map.of("fruit", "apple", "number", 1)) + .queryString("droid", List.of("C3PO", "R2D2")), listener); + + MockServer.Sse.lastRequest() + .assertParam("foo", "bar") + .assertParam("fruit", "apple") + .assertParam("number", "1") + .assertParam("droid", "C3PO") + .assertParam("droid", "R2D2"); + } + + @Test + void headers(){ + runWith(sse(MockServer.SSE) + .header("foo", "bar") + .header("qux", "zip") + .headerReplace("qux", "ok") + .headers(Map.of("fruit", "apple", "number", "1")) + .cookie("snack", "snickerdoodle") + , listener); + + MockServer.Sse.lastRequest() + .assertHeader("Accept", "text/event-stream") + .assertHeader("foo", "bar") + .assertHeader("qux", "ok") + .assertHeader("number", "1") + .assertHeader("fruit", "apple") + .assertCookie("snack", "snickerdoodle"); + } + + @Test + void canReplaceTheDefaultAcceptsHeader(){ + runWith(sse(MockServer.SSE) + .header("Accept", "application/json") + , listener); + + MockServer.Sse.lastRequest() + .assertHeader("Accept", "application/json"); + } + + private void runWith(SseRequest sse, TestHandler tl) { + try { + pool.submit(() -> { + var future = sse.connect(tl); + while(!future.isDone()){ + // waitin' + } + }); + + Thread.sleep(1000); + }catch (Exception e){ + throw new RuntimeException(e); + } + } + + private void sleep(int i) { + try { + Thread.sleep(i); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + +} \ No newline at end of file diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/SSEStreamTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/SSEStreamTest.java new file mode 100644 index 000000000..5d6bca0f9 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/SSEStreamTest.java @@ -0,0 +1,113 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.Unirest; +import kong.unirest.core.java.Event; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SSEStreamTest extends BddTest { + + @BeforeEach + @Override + public void setUp() { + super.setUp(); + MockServer.Sse.keepAlive(false); + } + + @Test + void simpleStreamdEvents() { + MockServer.Sse.queueEvent("1", "message", "hi mom"); + MockServer.Sse.queueEvent("2", "message", "i like cheese"); + + var events = Unirest.sse(MockServer.SSE) + .connect() + .collect(Collectors.toList()); + + assertThat(events) + .hasSize(3) + .containsExactly( + new Event("", "connect", "Welcome to Server Sent Events"), + new Event("1", "message", "hi mom"), + new Event("2", "message", "i like cheese") + ); + } + + @Test + void sendQueryParams() { + Unirest.sse(MockServer.SSE) + .queryString("foo", "bar") + .queryString(Map.of("fruit", "apple", "number", 1)) + .queryString("droid", List.of("C3PO", "R2D2")) + .connect() + .collect(Collectors.toList()); + + MockServer.lastRequest() + .assertParam("foo", "bar") + .assertParam("fruit", "apple") + .assertParam("number", "1") + .assertParam("droid", "C3PO") + .assertParam("droid", "R2D2"); + } + + @Test + void sendHeaders() { + Unirest.sse(MockServer.SSE) + .header("foo", "bar") + .header("qux", "zip") + .headerReplace("qux", "ok") + .headers(Map.of("fruit", "apple", "number", "1")) + .cookie("snack", "snickerdoodle") + .connect() + .collect(Collectors.toList()); + + MockServer.lastRequest() + .assertHeader("Accept", "text/event-stream") + .assertHeader("foo", "bar") + .assertHeader("qux", "ok") + .assertHeader("number", "1") + .assertHeader("fruit", "apple") + .assertCookie("snack", "snickerdoodle"); + } + + @Test + void canSendLastEventIdHeader() { + Unirest.sse(MockServer.SSE) + .lastEventId("42") + .connect() + .collect(Collectors.toList()); + + MockServer.lastRequest() + .assertHeader("Last-Event-ID", "42"); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/SseRealExamples.java b/unirest-bdd-tests/src/test/java/BehaviorTests/SseRealExamples.java new file mode 100644 index 000000000..edd78dfb4 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/SseRealExamples.java @@ -0,0 +1,54 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + + +import BehaviorTests.wikipedia.RecentChange; +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +@Disabled +public class SseRealExamples { + + @Test + void sync() { + Unirest.sse("https://stream.wikimedia.org/v2/stream/recentchange") + .connect() + .map(event -> event.asObject(RecentChange.class)) + .forEach(change -> System.out.println("Changed Page: " + change.getTitle())); + } + + @Test + void async() { + var future = Unirest.sse("https://stream.wikimedia.org/v2/stream/recentchange") + .connect(event -> { + var change = event.asObject(RecentChange.class); + System.out.println("Changed Page: " + change.getTitle()); + }); + while (!future.isDone()){} + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/TestHandler.java b/unirest-bdd-tests/src/test/java/BehaviorTests/TestHandler.java new file mode 100644 index 000000000..49b0e35b8 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/TestHandler.java @@ -0,0 +1,67 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.SseHandler; +import kong.unirest.core.java.Event; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TestHandler implements SseHandler { + private final List comments = Collections.synchronizedList(new ArrayList<>()); + private final List events = Collections.synchronizedList(new ArrayList<>()); + + TestHandler assertHasComment(String comment) { + assertThat(comments) + .contains(comment); + return this; + } + + public TestHandler assertHasEvent(String event, String content) { + return assertHasEvent("", event, content); + } + + public TestHandler assertHasEvent(String id, String event, String content) { + assertThat(events) + .contains(new Event(id, event, content, null)); + return this; + } + + + @Override + public void onEvent(Event event) { + events.add(event); + } + + @Override + public void onComment(String line) { + comments.add(line); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/TestMonitor.java b/unirest-bdd-tests/src/test/java/BehaviorTests/TestMonitor.java new file mode 100644 index 000000000..5548e87ac --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/TestMonitor.java @@ -0,0 +1,99 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import com.google.common.base.Strings; +import kong.unirest.core.ProgressMonitor; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import static BehaviorTests.TestUtil.rezFile; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class TestMonitor implements ProgressMonitor { + public File spidey = rezFile("/spidey.jpg"); + + private Map stats = new HashMap<>(); + + @Override + public void accept(String field, String file, Long bytesWritten, Long totalBytes) { + String key = firstNotEmpty(file, field); + stats.compute(key, (f, s) -> { + s = defaultIfNull(s, Stats::new); + s.progress.add(bytesWritten); + s.timesCalled++; + s.total = totalBytes; + return s; + }); + } + + private static T defaultIfNull(T t, Supplier supplier) { + if(t == null){ + return supplier.get(); + } + return t; + } + + private String firstNotEmpty(String... s) { + return Stream.of(s) + .filter(string -> !Strings.isNullOrEmpty(string)) + .findFirst() + .orElse(""); + } + + public Stats get(String fineName) { + return stats.getOrDefault(fineName, new Stats()); + } + + public void assertSpideyFileUpload() { + assertSpideyFileUpload(spidey.getName()); + } + + public void assertSpideyFileUpload(String name) { + Stats stat = get(name); + assertEquals(spidey.length(), stat.total, "Wrong Expected Length"); + assertTrue(stat.timesCalled > 1); + } + + public void assertSpideyFileDownload(String name) { + Stats stat = get(name); + assertEquals(spidey.length(), stat.total, "Wrong Expected Length"); + assertTrue(stat.timesCalled > 1); + } + + static class Stats { + List progress = new ArrayList<>(); + long total; + long timesCalled; + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/TestSSEConsumer.java b/unirest-bdd-tests/src/test/java/BehaviorTests/TestSSEConsumer.java new file mode 100644 index 000000000..080413f30 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/TestSSEConsumer.java @@ -0,0 +1,89 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import io.javalin.http.sse.SseClient; +import kong.unirest.core.java.Event; + +import java.util.ArrayList; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.function.Consumer; + +public class TestSSEConsumer implements Consumer { + private static final Queue clients = new ConcurrentLinkedDeque<>(); + private static final List queuedEvents = new ArrayList<>(); + private static RequestCapture lastRequest; + private static boolean keepAlive = true; + + public static void reset(){ + clients.clear(); + queuedEvents.clear(); + keepAlive = true; + } + + public static RequestCapture getLastRequest() { + return lastRequest; + } + + public static void sendComment(String message) { + clients.forEach(c -> c.sendComment(message)); + } + + public static void sendEvent(String data) { + clients.forEach(c -> c.sendEvent(data)); + } + + public static void sendEvent(String id, String event, String content) { + clients.forEach(c -> c.sendEvent(event, content, id)); + } + + public static void sendEvent(String event, String content) { + clients.forEach(c -> c.sendEvent(event, content)); + } + + public static void queueEvent(String id, String event, String content) { + queuedEvents.add(new Event(id, event, content, null)); + } + + public static void keepAlive(boolean value) { + keepAlive = value; + } + + @Override + public void accept(SseClient client) { + lastRequest = new RequestCapture(client.ctx()); + if(keepAlive) { + client.keepAlive(); + } + client.sendEvent("connect", "Welcome to Server Sent Events"); + clients.add(client); + queuedEvents.forEach(e -> { + client.sendEvent(e.event(), e.data(), e.id()); + }); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/TestUtil.java b/unirest-bdd-tests/src/test/java/BehaviorTests/TestUtil.java new file mode 100644 index 000000000..ae9415b22 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/TestUtil.java @@ -0,0 +1,164 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import tools.jackson.core.JacksonException; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.datatype.guava.GuavaModule; +import com.google.common.base.Charsets; +import com.google.common.collect.Multimap; +import com.google.common.io.Resources; +import kong.unirest.core.Client; +import kong.unirest.core.Unirest; +import kong.unirest.core.java.JavaClient; +import org.assertj.guava.api.MultimapAssert; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.*; +import java.net.http.HttpClient; +import java.net.http.HttpResponse; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +import static org.assertj.core.api.Fail.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class TestUtil { + private static final ObjectMapper om = JsonMapper.builderWithJackson2Defaults().addModule(new GuavaModule()).build(); + + public static T readValue(String body, Class as) { + try { + return om.readValue(body, as); + } catch (JacksonException e) { + throw new RuntimeException(e); + } + } + + public static File getImageFile() { + return rezFile("/image.jpg"); + } + + public static File rezFile(String name) { + try { + return new File(MockServer.class.getResource(name).toURI()); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + + public static InputStream rezInput(String name) { + try { + return MockServer.class.getResourceAsStream(name); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static byte[] getFileBytes(String s) { + try { + final InputStream stream = new FileInputStream(rezFile(s)); + final byte[] bytes = new byte[stream.available()]; + stream.read(bytes); + stream.close(); + return bytes; + }catch (IOException e){ + throw new RuntimeException(e); + } + } + + public static Map mapOf(Object... keyValues) { + Map map = new HashMap<>(); + + K key = null; + for (int index = 0; index < keyValues.length; index++) { + if (index % 2 == 0) { + key = (K)keyValues[index]; + } + else { + map.put(key, (V)keyValues[index]); + } + } + + return map; + } + + public static String getResource(String resourceName) { + try { + return Resources.toString(Resources.getResource(resourceName), Charsets.UTF_8); + }catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static Client getFailureClient() throws Exception { + HttpClient mock = mock(HttpClient.class); + when(mock.send(any(java.net.http.HttpRequest.class), + any(HttpResponse.BodyHandler.class))) + .thenThrow(new IOException("Something horrible happened")); + return new JavaClient(Unirest.config(), mock); + } + + public static void reset() { + + } + + public static Client getFailureAsyncClient() { + HttpClient client = HttpClient.newBuilder() + //.sslContext(new SSLContext()) + .authenticator(new Authenticator() { + @Override + public PasswordAuthentication requestPasswordAuthenticationInstance(String host, InetAddress addr, int port, String protocol, String prompt, String scheme, URL url, RequestorType reqType) { + throw new RuntimeException("boo"); + } + }).build(); + return new JavaClient(Unirest.config(), client); + } + + public static MultimapAssert assertMultiMap(final Multimap actual) { + return org.assertj.guava.api.Assertions.assertThat(actual); + } + + public static final class Matchers { + public static Consumer isBase64Encoded() { + return h -> { + var value = h.getValue(); + try { + Base64.getDecoder().decode(value); + }catch (IllegalArgumentException e){ + fail("Header was not base64 encoded: " + value); + } + }; + } + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/TimeoutTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/TimeoutTest.java new file mode 100644 index 000000000..aafaf9b4b --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/TimeoutTest.java @@ -0,0 +1,132 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + + +package BehaviorTests; + +import kong.unirest.core.Config; +import kong.unirest.core.Unirest; +import kong.unirest.core.UnirestException; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.net.http.HttpConnectTimeoutException; +import java.net.http.HttpTimeoutException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.jupiter.api.Assertions.*; + + +class TimeoutTest extends BddTest { + + @Test + void requestTmeouts() { + // prime the server + Unirest.get(MockServer.GET).asEmpty(); + + var ex = assertThrows(UnirestException.class, () -> { + Unirest.get(MockServer.TIMEOUT) + .requestTimeout(5) + .asString(); + }); + assertEquals(HttpTimeoutException.class, ex.getCause().getClass()); + } + + @Test + void requestTmeoutsViaGlobalConfig() { + // prime the server + Unirest.get(MockServer.GET).asEmpty(); + + Unirest.config().requestTimeout(5); + + var ex = assertThrows(UnirestException.class, () -> { + Unirest.get(MockServer.TIMEOUT) + .asString(); + }); + assertEquals(HttpTimeoutException.class, ex.getCause().getClass()); + } + + @Test @Disabled + void testSetTimeouts() { + String address = MockServer.GET; + long start = System.currentTimeMillis(); + try { + Unirest.get(address).asString(); + } catch (Exception e) { + if (System.currentTimeMillis() - start > Config.DEFAULT_CONNECT_TIMEOUT + 100) { // Add 100ms for code execution + fail(); + } + } + Unirest.config().reset(); + Unirest.config().connectTimeout(2000); + + start = System.currentTimeMillis(); + try { + Unirest.get(address).asString(); + } catch (Exception e) { + if (System.currentTimeMillis() - start > 2100) { // Add 100ms for code execution + fail(); + } + } + } + + @Test + @Disabled // this is flakey + void parallelTest() throws InterruptedException { + Unirest.config().connectTimeout(10); + + long start = System.currentTimeMillis(); + makeParallelRequests(); + long smallerConcurrencyTime = (System.currentTimeMillis() - start); + + Unirest.config().connectTimeout(200); + start = System.currentTimeMillis(); + makeParallelRequests(); + long higherConcurrencyTime = (System.currentTimeMillis() - start); + + assertTrue(higherConcurrencyTime < smallerConcurrencyTime); + } + + private void makeParallelRequests() throws InterruptedException { + ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(10); + final AtomicInteger counter = new AtomicInteger(0); + for (int i = 0; i < 200; i++) { + newFixedThreadPool.execute(() -> { + try { + Unirest.get(MockServer.GET).queryString("index", counter.incrementAndGet()).asString(); + } catch (UnirestException e) { + throw new RuntimeException(e); + } + }); + } + + newFixedThreadPool.shutdown(); + newFixedThreadPool.awaitTermination(10, TimeUnit.MINUTES); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/UniBodyPostingTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/UniBodyPostingTest.java new file mode 100644 index 000000000..00f4e63de --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/UniBodyPostingTest.java @@ -0,0 +1,257 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.JsonNode; +import kong.unirest.core.RequestBodyEntity; +import kong.unirest.core.Unirest; +import kong.unirest.core.UnirestConfigException; +import kong.unirest.core.json.JSONArray; +import kong.unirest.core.json.JSONObject; +import org.junit.jupiter.api.Test; + +import java.nio.charset.StandardCharsets; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class UniBodyPostingTest extends BddTest { + + @Test + void hasShortCutForContentHeader() { + Unirest.post(MockServer.POST) + .contentType("plain/text") + .body("Hi") + .asObject(RequestCapture.class) + .getBody() + .assertContentType("plain/text"); + } + + @Test + void contentTypeAfterTheBody() { + Unirest.post(MockServer.POST) + .body("Hi") + .contentType("plain/text") + .asObject(RequestCapture.class) + .getBody() + .assertContentType("plain/text"); + } + + @Test + void testDefaults_String(){ + Unirest.post(MockServer.POST) + .body("foo") + .asObject(RequestCapture.class) + .getBody() + .assertRawContentType("text/plain; charset=utf-8"); + } + + @Test + void canSetCharsetOfBody(){ + Unirest.post(MockServer.POST) + .charset(StandardCharsets.US_ASCII) + .body("foo") + .asObject(RequestCapture.class) + .getBody() + .assertContentType("text/plain", "charset", "US-ASCII") + .assertBody("foo") + .assertCharset(StandardCharsets.US_ASCII); + } + + @Test + void canSetCharsetOfBodyAfterMovingToBody(){ + Unirest.post(MockServer.POST) + .body("foo") + .charset(StandardCharsets.US_ASCII) + .asObject(RequestCapture.class) + .getBody() + .assertRawContentType("text/plain; charset=US-ASCII") + .assertBody("foo") + .assertCharset(StandardCharsets.US_ASCII); + } + + @Test + void testPostRawBody() { + var sourceString = "'\"@こんにづは-test-123-" + Math.random(); + var sentBytes = sourceString.getBytes(); + + Unirest.post(MockServer.POST) + .body(sentBytes) + .asObject(RequestCapture.class) + .getBody() + .assertBody(sourceString); + } + + @Test + void testAsyncCustomContentType() { + Unirest.post(MockServer.POST) + .header("accept", "application/json") + .header("Content-Type", "application/json") + .body("{\"hello\":\"world\"}") + .asJsonAsync(new MockCallback<>(this, r -> parse(r) + .assertBody("{\"hello\":\"world\"}") + .assertHeader("Content-Type", "application/json")) + ); + + assertAsync(); + } + + + @Test + void postAPojoObjectUsingTheMapper() { + var postResponseMock = new GetResponse(); + postResponseMock.setUrl(MockServer.POST); + + Unirest.post(postResponseMock.getUrl()) + .header("accept", "application/json") + .header("Content-Type", "application/json") + .body(postResponseMock) + .asObject(RequestCapture.class) + .getBody() + .assertBody("{\"url\":\"http://localhost:4567/post\"}"); + } + + @Test + void testDeleteBody() { + var body = "{\"jsonString\":{\"members\":\"members1\"}}"; + Unirest.delete(MockServer.DELETE) + .body(body) + .asObject(RequestCapture.class) + .getBody() + .assertBody(body); + } + + @Test + void postBodyAsJson() { + var body = new JSONObject(); + body.put("krusty","krab"); + + Unirest.post(MockServer.POST) + .body(body) + .asObject(RequestCapture.class) + .getBody() + .assertBody("{\"krusty\":\"krab\"}"); + } + + @Test + void postBodyAsJsonArray() { + var body = new JSONArray(); + body.put(0, "krusty"); + body.put(1, "krab"); + + Unirest.post(MockServer.POST) + .body(body) + .asObject(RequestCapture.class) + .getBody() + .assertBody("[\"krusty\",\"krab\"]"); + } + + @Test + void postBodyAsJsonNode() { + var body = new JsonNode("{\"krusty\":\"krab\"}"); + + Unirest.post(MockServer.POST) + .body(body) + .asObject(RequestCapture.class) + .getBody() + .assertBody("{\"krusty\":\"krab\"}"); + } + + @Test + void cantPostObjectWithoutObjectMapper(){ + Unirest.config().setObjectMapper(null); + + assertThatThrownBy(() -> Unirest.post(MockServer.POST).body(new Foo("die"))) + .isInstanceOf(UnirestConfigException.class) + .hasMessage("No Object Mapper Configured. Please config one with Unirest.config().setObjectMapper"); + } + + @Test + void theRequestBodyIsAString() { + var request = Unirest.post(MockServer.POST) + .basicAuth("foo", "bar") + .header("Content-Type", "application/json") + .queryString("foo", "bar") + .body("{\"body\": \"sample\"}"); + + var value = request.getBody().get().uniPart().getValue(); + assertEquals("{\"body\": \"sample\"}", value); + } + + @Test + void stringPassedToObjectGetsPassedToString() { + Object body = "{\"body\": \"sample\"}"; + RequestBodyEntity request = Unirest.post(MockServer.POST) + .basicAuth("foo", "bar") + .header("Content-Type", "application/json") + .queryString("foo", "bar") + .body(body); + + var value = request.getBody().get().uniPart().getValue(); + assertEquals("{\"body\": \"sample\"}", value); + } + + @Test + void jsonNodePassedToObjectGetsPassedToString() { + Object body = new JsonNode("{\"body\": \"sample\"}"); + RequestBodyEntity request = Unirest.post(MockServer.POST) + .basicAuth("foo", "bar") + .header("Content-Type", "application/json") + .queryString("foo", "bar") + .body(body); + + var value = request.getBody().get().uniPart().getValue(); + assertEquals("{\"body\":\"sample\"}", value); + } + + @Test + void jsonObjectPassedToObjectGetsPassedToString() { + Object body = new JSONObject("{\"body\": \"sample\"}"); + RequestBodyEntity request = Unirest.post(MockServer.POST) + .basicAuth("foo", "bar") + .header("Content-Type", "application/json") + .queryString("foo", "bar") + .body(body); + + var value = request.getBody().get().uniPart().getValue(); + assertEquals("{\"body\":\"sample\"}", value); + } + + @Test + void jsonArrayPassedToObjectGetsPassedToString() { + Object body = new JSONArray("[\"body\", \"sample\"]"); + RequestBodyEntity request = Unirest.post(MockServer.POST) + .basicAuth("foo", "bar") + .header("Content-Type", "application/json") + .queryString("foo", "bar") + .body(body); + + var value = request.getBody().get().uniPart().getValue(); + assertEquals("[\"body\",\"sample\"]", value); + } + +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/UploadProgressTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/UploadProgressTest.java new file mode 100644 index 000000000..1f754063a --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/UploadProgressTest.java @@ -0,0 +1,107 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.FileInputStream; + +import static java.util.Arrays.asList; +import static BehaviorTests.TestUtil.rezFile; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class UploadProgressTest extends BddTest { + + private TestMonitor monitor; + + @Override @BeforeEach + public void setUp() { + super.setUp(); + this.monitor = new TestMonitor(); + } + + @Test + void canAddUploadProgress() { + Unirest.post(MockServer.POST) + .field("spidey", monitor.spidey) + .uploadMonitor(monitor) + .asEmpty(); + + monitor.assertSpideyFileUpload(); + } + + @Test + void canAddUploadProgressAsync() throws Exception { + Unirest.post(MockServer.POST) + .field("spidey", monitor.spidey) + .uploadMonitor(monitor) + .asEmpty(); + + monitor.assertSpideyFileUpload(); + } + + @Test + void canKeepTrackOfMultipleFiles() { + Unirest.post(MockServer.POST) + .field("spidey", monitor.spidey) + .field("other", rezFile("/test.txt")) + .uploadMonitor(monitor) + .asEmpty(); + + monitor.assertSpideyFileUpload(); + assertOtherFileUpload(); + } + + @Test + void canMonitorIfPassedAsInputStream() throws Exception { + Unirest.post(MockServer.POST) + .field("spidey", new FileInputStream(monitor.spidey)) + .uploadMonitor(monitor) + .asEmpty(); + + monitor.assertSpideyFileUpload("spidey"); + } + + @Test + void canUseWithInputStreamBody() throws Exception { + Unirest.post(MockServer.POST) + .body(new FileInputStream(monitor.spidey)) + .uploadMonitor(monitor) + .asEmpty(); + + monitor.assertSpideyFileUpload("body"); + } + + private void assertOtherFileUpload() { + var stat = monitor.get("test.txt"); + assertEquals(1, stat.timesCalled); + assertEquals(asList(19L), stat.progress); + assertEquals(19L, stat.total); + } + +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/VerbTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/VerbTest.java new file mode 100644 index 000000000..34d4697a9 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/VerbTest.java @@ -0,0 +1,102 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.HttpMethod; +import kong.unirest.core.HttpResponse; +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class VerbTest extends BddTest { + + @Test + void get() { + Unirest.get(MockServer.GET) + .asObject(RequestCapture.class) + .getBody() + .assertMethod(HttpMethod.GET); + } + + @Test + void post() { + Unirest.post(MockServer.POST) + .asObject(RequestCapture.class) + .getBody() + .assertMethod(HttpMethod.POST); + } + + @Test + void put() { + Unirest.put(MockServer.POST) + .asObject(RequestCapture.class) + .getBody() + .assertMethod(HttpMethod.PUT); + } + + @Test + void patch() { + Unirest.patch(MockServer.PATCH) + .asObject(RequestCapture.class) + .getBody() + .assertMethod(HttpMethod.PATCH); + } + + @Test + void head() { + var response = Unirest.head(MockServer.GET).asEmpty(); + + assertEquals(200, response.getStatus()); + assertEquals("text/plain;charset=utf-8", response.getHeaders().getFirst("Content-Type")); + } + + @Test + void option() { + Unirest.options(MockServer.GET) + .asObject(RequestCapture.class) + .getBody() + .assertMethod(HttpMethod.OPTIONS); + } + + @Test + void weirdVerbs() { + Unirest.request("CHEESE", MockServer.CHEESE) + .asObject(RequestCapture.class) + .getBody() + .assertMethod(HttpMethod.valueOf("CHEESE")) + .assertBody(""); + } + + @Test + void sendBodyToGet() { + Unirest.request("GET", MockServer.GET) + .body("Hi mom") + .asObject(RequestCapture.class) + .getBody() + .assertBody("Hi mom"); + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/WebsocketTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/WebsocketTest.java new file mode 100644 index 000000000..1731dcf38 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/WebsocketTest.java @@ -0,0 +1,87 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package BehaviorTests; + +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; + +import java.net.http.WebSocket; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletionStage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +public class WebsocketTest extends BddTest { + @Test + void makeAWebSocket() throws Exception { + var listener = new TheListener(); + MockServer.WebSocketHandler.expectOpeningMessage("Darkness Rises When Silence Dies"); + assertEquals("ws://localhost:4567/websocket", MockServer.WEBSOCKET); + Unirest.webSocket(MockServer.WEBSOCKET) + .connect(listener) + .socket() + .get() + .sendText("...", false); + Thread.sleep(50); + assertTrue(listener.isOpen); + assertEquals("Darkness Rises When Silence Dies", listener.messages.get(0)); + + } + + + @Test + void sendsHeaders() throws Exception { + assertEquals("ws://localhost:4567/websocket", MockServer.WEBSOCKET); + Unirest.webSocket(MockServer.WEBSOCKET) + .header("Authentication", "Sanguine, my Brother.") + .connect(new TheListener()) + .socket() + .get(); + + assertEquals("Sanguine, my Brother.", + MockServer.WebSocketHandler.headers.get("Authentication")); + } + + public class TheListener implements WebSocket.Listener { + public boolean isOpen = false; + public List messages = new ArrayList<>(); + + @Override + public void onOpen(WebSocket webSocket) { + this.isOpen = true; + WebSocket.Listener.super.onOpen(webSocket); + } + + @Override + public CompletionStage onText(WebSocket webSocket, CharSequence data, boolean last) { + messages.add(String.valueOf(data)); + return WebSocket.Listener.super.onText(webSocket, data, last); + } + } +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/wikipedia/Length.java b/unirest-bdd-tests/src/test/java/BehaviorTests/wikipedia/Length.java new file mode 100644 index 000000000..20c6d58c8 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/wikipedia/Length.java @@ -0,0 +1,65 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + +package BehaviorTests.wikipedia; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "old", + "new" +}) +public class Length { + + @JsonProperty("old") + private Long old; + @JsonProperty("new") + private Long _new; + + @JsonProperty("old") + public Long getOld() { + return old; + } + + @JsonProperty("old") + public void setOld(Long old) { + this.old = old; + } + + @JsonProperty("new") + public Long getNew() { + return _new; + } + + @JsonProperty("new") + public void setNew(Long _new) { + this._new = _new; + } + +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/wikipedia/Meta.java b/unirest-bdd-tests/src/test/java/BehaviorTests/wikipedia/Meta.java new file mode 100644 index 000000000..fb5de394b --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/wikipedia/Meta.java @@ -0,0 +1,157 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + +package BehaviorTests.wikipedia; + + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "uri", + "request_id", + "id", + "dt", + "domain", + "stream", + "topic", + "partition", + "offset" +}) +public class Meta { + + @JsonProperty("uri") + private String uri; + @JsonProperty("request_id") + private String requestId; + @JsonProperty("id") + private String id; + @JsonProperty("dt") + private String dt; + @JsonProperty("domain") + private String domain; + @JsonProperty("stream") + private String stream; + @JsonProperty("topic") + private String topic; + @JsonProperty("partition") + private Long partition; + @JsonProperty("offset") + private Long offset; + + @JsonProperty("uri") + public String getUri() { + return uri; + } + + @JsonProperty("uri") + public void setUri(String uri) { + this.uri = uri; + } + + @JsonProperty("request_id") + public String getRequestId() { + return requestId; + } + + @JsonProperty("request_id") + public void setRequestId(String requestId) { + this.requestId = requestId; + } + + @JsonProperty("id") + public String getId() { + return id; + } + + @JsonProperty("id") + public void setId(String id) { + this.id = id; + } + + @JsonProperty("dt") + public String getDt() { + return dt; + } + + @JsonProperty("dt") + public void setDt(String dt) { + this.dt = dt; + } + + @JsonProperty("domain") + public String getDomain() { + return domain; + } + + @JsonProperty("domain") + public void setDomain(String domain) { + this.domain = domain; + } + + @JsonProperty("stream") + public String getStream() { + return stream; + } + + @JsonProperty("stream") + public void setStream(String stream) { + this.stream = stream; + } + + @JsonProperty("topic") + public String getTopic() { + return topic; + } + + @JsonProperty("topic") + public void setTopic(String topic) { + this.topic = topic; + } + + @JsonProperty("partition") + public Long getPartition() { + return partition; + } + + @JsonProperty("partition") + public void setPartition(Long partition) { + this.partition = partition; + } + + @JsonProperty("offset") + public Long getOffset() { + return offset; + } + + @JsonProperty("offset") + public void setOffset(Long offset) { + this.offset = offset; + } + +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/wikipedia/RecentChange.java b/unirest-bdd-tests/src/test/java/BehaviorTests/wikipedia/RecentChange.java new file mode 100644 index 000000000..17803a7eb --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/wikipedia/RecentChange.java @@ -0,0 +1,314 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + +package BehaviorTests.wikipedia; + + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "$schema", + "meta", + "id", + "type", + "namespace", + "title", + "title_url", + "comment", + "timestamp", + "user", + "bot", + "notify_url", + "minor", + "patrolled", + "length", + "revision", + "server_url", + "server_name", + "server_script_path", + "wiki", + "parsedcomment" +}) + +public class RecentChange { + + @JsonProperty("$schema") + private String $schema; + @JsonProperty("meta") + private Meta meta; + @JsonProperty("id") + private Long id; + @JsonProperty("type") + private String type; + @JsonProperty("namespace") + private Long namespace; + @JsonProperty("title") + private String title; + @JsonProperty("title_url") + private String titleUrl; + @JsonProperty("comment") + private String comment; + @JsonProperty("timestamp") + private Long timestamp; + @JsonProperty("user") + private String user; + @JsonProperty("bot") + private Boolean bot; + @JsonProperty("notify_url") + private String notifyUrl; + @JsonProperty("minor") + private Boolean minor; + @JsonProperty("patrolled") + private Boolean patrolled; + @JsonProperty("length") + private Length length; + @JsonProperty("revision") + private Revision revision; + @JsonProperty("server_url") + private String serverUrl; + @JsonProperty("server_name") + private String serverName; + @JsonProperty("server_script_path") + private String serverScriptPath; + @JsonProperty("wiki") + private String wiki; + @JsonProperty("parsedcomment") + private String parsedcomment; + + @JsonProperty("$schema") + public String get$schema() { + return $schema; + } + + @JsonProperty("$schema") + public void set$schema(String $schema) { + this.$schema = $schema; + } + + @JsonProperty("meta") + public Meta getMeta() { + return meta; + } + + @JsonProperty("meta") + public void setMeta(Meta meta) { + this.meta = meta; + } + + @JsonProperty("id") + public Long getId() { + return id; + } + + @JsonProperty("id") + public void setId(Long id) { + this.id = id; + } + + @JsonProperty("type") + public String getType() { + return type; + } + + @JsonProperty("type") + public void setType(String type) { + this.type = type; + } + + @JsonProperty("namespace") + public Long getNamespace() { + return namespace; + } + + @JsonProperty("namespace") + public void setNamespace(Long namespace) { + this.namespace = namespace; + } + + @JsonProperty("title") + public String getTitle() { + return title; + } + + @JsonProperty("title") + public void setTitle(String title) { + this.title = title; + } + + @JsonProperty("title_url") + public String getTitleUrl() { + return titleUrl; + } + + @JsonProperty("title_url") + public void setTitleUrl(String titleUrl) { + this.titleUrl = titleUrl; + } + + @JsonProperty("comment") + public String getComment() { + return comment; + } + + @JsonProperty("comment") + public void setComment(String comment) { + this.comment = comment; + } + + @JsonProperty("timestamp") + public Long getTimestamp() { + return timestamp; + } + + @JsonProperty("timestamp") + public void setTimestamp(Long timestamp) { + this.timestamp = timestamp; + } + + @JsonProperty("user") + public String getUser() { + return user; + } + + @JsonProperty("user") + public void setUser(String user) { + this.user = user; + } + + @JsonProperty("bot") + public Boolean getBot() { + return bot; + } + + @JsonProperty("bot") + public void setBot(Boolean bot) { + this.bot = bot; + } + + @JsonProperty("notify_url") + public String getNotifyUrl() { + return notifyUrl; + } + + @JsonProperty("notify_url") + public void setNotifyUrl(String notifyUrl) { + this.notifyUrl = notifyUrl; + } + + @JsonProperty("minor") + public Boolean getMinor() { + return minor; + } + + @JsonProperty("minor") + public void setMinor(Boolean minor) { + this.minor = minor; + } + + @JsonProperty("patrolled") + public Boolean getPatrolled() { + return patrolled; + } + + @JsonProperty("patrolled") + public void setPatrolled(Boolean patrolled) { + this.patrolled = patrolled; + } + + @JsonProperty("length") + public Length getLength() { + return length; + } + + @JsonProperty("length") + public void setLength(Length length) { + this.length = length; + } + + @JsonProperty("revision") + public Revision getRevision() { + return revision; + } + + @JsonProperty("revision") + public void setRevision(Revision revision) { + this.revision = revision; + } + + @JsonProperty("server_url") + public String getServerUrl() { + return serverUrl; + } + + @JsonProperty("server_url") + public void setServerUrl(String serverUrl) { + this.serverUrl = serverUrl; + } + + @JsonProperty("server_name") + public String getServerName() { + return serverName; + } + + @JsonProperty("server_name") + public void setServerName(String serverName) { + this.serverName = serverName; + } + + @JsonProperty("server_script_path") + public String getServerScriptPath() { + return serverScriptPath; + } + + @JsonProperty("server_script_path") + public void setServerScriptPath(String serverScriptPath) { + this.serverScriptPath = serverScriptPath; + } + + @JsonProperty("wiki") + public String getWiki() { + return wiki; + } + + @JsonProperty("wiki") + public void setWiki(String wiki) { + this.wiki = wiki; + } + + @JsonProperty("parsedcomment") + public String getParsedcomment() { + return parsedcomment; + } + + @JsonProperty("parsedcomment") + public void setParsedcomment(String parsedcomment) { + this.parsedcomment = parsedcomment; + } + +} diff --git a/unirest-bdd-tests/src/test/java/BehaviorTests/wikipedia/Revision.java b/unirest-bdd-tests/src/test/java/BehaviorTests/wikipedia/Revision.java new file mode 100644 index 000000000..e03c5f72d --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/wikipedia/Revision.java @@ -0,0 +1,66 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + +package BehaviorTests.wikipedia; + + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "old", + "new" +}) +public class Revision { + + @JsonProperty("old") + private Long old; + @JsonProperty("new") + private Long _new; + + @JsonProperty("old") + public Long getOld() { + return old; + } + + @JsonProperty("old") + public void setOld(Long old) { + this.old = old; + } + + @JsonProperty("new") + public Long getNew() { + return _new; + } + + @JsonProperty("new") + public void setNew(Long _new) { + this._new = _new; + } + +} diff --git a/unirest-bdd-tests/src/test/java/kong/unirest/core/Clock.java b/unirest-bdd-tests/src/test/java/kong/unirest/core/Clock.java new file mode 100644 index 000000000..3a75ddd18 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/kong/unirest/core/Clock.java @@ -0,0 +1,41 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import kong.unirest.core.Util; + +import java.time.Instant; + +public class Clock { + public static void freeze(Instant now) { + Util.freezeClock(now); + } + + public static void reset() { + Util.resetClock(); + } + +} diff --git a/unirest-bdd-tests/src/test/java/kong/unirest/core/CookieParsingTest.java b/unirest-bdd-tests/src/test/java/kong/unirest/core/CookieParsingTest.java new file mode 100644 index 000000000..b4c661a7c --- /dev/null +++ b/unirest-bdd-tests/src/test/java/kong/unirest/core/CookieParsingTest.java @@ -0,0 +1,151 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.time.ZoneId; +import java.time.ZonedDateTime; + +import static org.junit.jupiter.api.Assertions.*; + +class CookieParsingTest { + + @Test + void parseFull() { + Cookie c = new Cookie("color=blue;Path=/get;Domain=localhost;Expires=Sun, 05-Jan-2020 15:00:20 GMT;Max-Age=42;HttpOnly"); + assertEquals("blue", c.getValue()); + assertEquals("localhost", c.getDomain()); + assertEquals("/get", c.getPath()); + assertEquals(ZonedDateTime.of(2020,1,5,15,0,20, 0, ZoneId.of("GMT")), + c.getExpiration()); + assertTrue(c.isHttpOnly()); + assertFalse(c.isSecure()); + assertEquals(42, c.getMaxAge()); + assertNull(c.getSameSite()); + } + + @Test + void alternateDate() { + Cookie c = new Cookie("color=blue;Path=/get;Domain=localhost;Expires=Sun, 05 Jan 2020 15:00:20 GMT;Max-Age=42;HttpOnly"); + assertEquals(ZonedDateTime.of(2020,1,5,15,0,20, 0, ZoneId.of("GMT")), + c.getExpiration()); + } + + @Test + void partitionFlag() { + assertFalse(new Cookie("").isPartitioned()); + assertTrue(new Cookie("color=blue;Partitioned;").isPartitioned()); + } + + @Test + void canSetPartition() { + var cookie = new Cookie("cookie", "snickerdoodle"); + cookie.setPartitioned(true); + assertEquals("cookie=snickerdoodle;Partitioned", cookie.toString()); + } + + @Test + void canSetSecured() { + var cookie = new Cookie("cookie", "snickerdoodle"); + cookie.setSecured(true); + assertEquals("cookie=snickerdoodle;Secure", cookie.toString()); + } + + @Test + void parseBackOutToString() { + String v = "color=blue;Path=/get;Domain=localhost;Expires=Sun, 05-Jan-2020 15:00:20 GMT;Max-Age=42;HttpOnly"; + Cookie c = new Cookie(v); + assertEquals(v, c.toString()); + } + + @Test + void sameSite() { + String v = "color=blue;SameSite=Strict"; + Cookie c = new Cookie(v); + assertEquals(Cookie.SameSite.Strict, c.getSameSite()); + } + + @Test + void secure() { + String v = "color=blue;Secure"; + Cookie c = new Cookie(v); + assertTrue(c.isSecure()); + assertEquals("color=blue;Secure", c.toString()); + } + + @Test + void futurePropsDontBreak() { + String v = "color=blue;TheFuture"; + Cookie c = new Cookie(v); + assertEquals("color=blue", c.toString()); + } + + @Test + void emptyValue() { + String v = "SignOnDefault=; domain=.admin.virginia.edu; path=/; HttpOnly"; + Cookie c = new Cookie(v); + assertEquals("", c.getValue()); + assertTrue(c.isHttpOnly()); + assertEquals(".admin.virginia.edu", c.getDomain()); + } + + @Test + void theValieCanBeDoubleQuoted() { + String v = "SignOnDefault=\" woh \";"; + Cookie c = new Cookie(v); + assertEquals(" woh ", c.getValue()); + } + + @Test + void justEmptyQuotes() { + String v = "SignOnDefault=\"\";"; + Cookie c = new Cookie(v); + assertEquals("", c.getValue()); + } + + @Test + void valuesCanHaveEquals() { + String v = "SignOnDefault====;"; + Cookie c = new Cookie(v); + assertEquals("===", c.getValue()); + } + + @Test + void justOneQuote() { + String v = "SignOnDefault=\";"; + Cookie c = new Cookie(v); + assertEquals("\"", c.getValue()); + } + + @Test + void justOneSideOfquotes() { + String v = "SignOnDefault=\"foo;"; + Cookie c = new Cookie(v); + assertEquals("\"foo", c.getValue()); + } +} diff --git a/unirest-bdd-tests/src/test/java/kong/unirest/core/EventSerializationTest.java b/unirest-bdd-tests/src/test/java/kong/unirest/core/EventSerializationTest.java new file mode 100644 index 000000000..f91f44c37 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/kong/unirest/core/EventSerializationTest.java @@ -0,0 +1,63 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import BehaviorTests.Foo; +import BehaviorTests.JacksonObjectMapper; +import kong.unirest.core.java.Event; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class EventSerializationTest { + @Test + void canDeserialize() { + var config = new Config(); + var om = new JacksonObjectMapper(); + config.setObjectMapper(om); + + var foo = new Foo("qux"); + var event = new Event("", "message", om.writeValue(foo), config); + + assertThat(event.asObject(Foo.class)) + .isEqualTo(foo); + } + + @Test + void canDeserializeWithGenericTypes(){ + var config = new Config(); + var om = new JacksonObjectMapper(); + config.setObjectMapper(om); + + var foo = List.of(new Foo("qux")); + var event = new Event("", "message", om.writeValue(foo), config); + + assertThat(event.asObject(new GenericType>() {})) + .isEqualTo(foo); + } +} diff --git a/unirest-bdd-tests/src/test/java/kong/unirest/core/JsonNodeTest.java b/unirest-bdd-tests/src/test/java/kong/unirest/core/JsonNodeTest.java new file mode 100644 index 000000000..bb8247b1d --- /dev/null +++ b/unirest-bdd-tests/src/test/java/kong/unirest/core/JsonNodeTest.java @@ -0,0 +1,61 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + + +import kong.unirest.core.JsonNode; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class JsonNodeTest { + + @Test + void canParseARegularObject() { + String json = "{\"foo\":\"bar\"}"; + JsonNode node = new JsonNode(json); + assertFalse(node.isArray()); + assertEquals("bar", node.getObject().getString("foo")); + assertEquals("bar", node.getArray().getJSONObject(0).getString("foo")); + assertEquals(json, node.toString()); + } + + @Test + void canParseArrayObject() { + String json = "[{\"foo\":\"bar\"}]"; + JsonNode node = new JsonNode(json); + assertTrue(node.isArray()); + assertEquals("bar", node.getArray().getJSONObject(0).getString("foo")); + assertNull(node.getObject()); + assertEquals(json, node.toString()); + } + + @Test + void nullAndEmptyObjectsResultInEmptyJson() { + assertEquals("{}", new JsonNode("").toString()); + assertEquals("{}", new JsonNode(null).toString()); + } +} \ No newline at end of file diff --git a/unirest-bdd-tests/src/test/java/kong/unirest/core/JsonPatchItemTest.java b/unirest-bdd-tests/src/test/java/kong/unirest/core/JsonPatchItemTest.java new file mode 100644 index 000000000..1a2cce850 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/kong/unirest/core/JsonPatchItemTest.java @@ -0,0 +1,90 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import com.google.common.collect.Sets; +import kong.unirest.core.JsonPatchItem; +import org.junit.jupiter.api.Test; + +import java.util.Set; + +import static kong.unirest.core.JsonPatchOperation.*; +import static org.junit.jupiter.api.Assertions.*; + +class JsonPatchItemTest { + + @Test + void patchOperationsAreUniqueByTheirElements() { + assertEquals( + new JsonPatchItem(add, "/foo", "bar"), + new JsonPatchItem(add, "/foo", "bar") + ); + + assertEquals( + new JsonPatchItem(remove, "/foo"), + new JsonPatchItem(remove, "/foo") + ); + + assertNotEquals( + new JsonPatchItem(add, "/foo", "bar"), + new JsonPatchItem(add, "/foo", "baz") + ); + + assertNotEquals( + new JsonPatchItem(add, "/foo", "bar"), + new JsonPatchItem(replace, "/foo", "bar") + ); + + assertNotEquals( + new JsonPatchItem(remove, "/foo"), + new JsonPatchItem(remove, "/foo", "baz") + ); + } + + @Test + void testHash() { + Set s = Sets.newHashSet(new JsonPatchItem(add, "/foo", "bar"), + new JsonPatchItem(add, "/foo", "bar")); + + assertEquals(1, s.size()); + } + + @Test + void edgeEquals() { + JsonPatchItem i = new JsonPatchItem(add, "/foo", "bar"); + assertEquals(i, i); + assertNotEquals(null, i); + assertNotEquals(new Object(), i); + } + + @Test + void basicTest() { + JsonPatchItem i = new JsonPatchItem(add, "/foo", "bar"); + assertEquals(add, i.getOp()); + assertEquals("/foo", i.getPath()); + assertEquals("bar", i.getValue()); + } +} \ No newline at end of file diff --git a/unirest-bdd-tests/src/test/resources/certs/badssl.com-client.p12 b/unirest-bdd-tests/src/test/resources/certs/badssl.com-client.p12 new file mode 100644 index 000000000..7eac07357 Binary files /dev/null and b/unirest-bdd-tests/src/test/resources/certs/badssl.com-client.p12 differ diff --git a/unirest-bdd-tests/src/test/resources/certs/badssl.com-client.pem b/unirest-bdd-tests/src/test/resources/certs/badssl.com-client.pem new file mode 100644 index 000000000..a7120a586 --- /dev/null +++ b/unirest-bdd-tests/src/test/resources/certs/badssl.com-client.pem @@ -0,0 +1,64 @@ +Bag Attributes + localKeyID: E2 03 61 76 14 F3 BF 5C E2 23 B3 3A 02 65 D6 D7 4E 36 13 2B +subject=/C=US/ST=California/L=San Francisco/O=BadSSL/CN=BadSSL Client Certificate +issuer=/C=US/ST=California/L=San Francisco/O=BadSSL/CN=BadSSL Client Root Certificate Authority +-----BEGIN CERTIFICATE----- +MIIEnTCCAoWgAwIBAgIJAINw7949autRMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNV +BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNp +c2NvMQ8wDQYDVQQKDAZCYWRTU0wxMTAvBgNVBAMMKEJhZFNTTCBDbGllbnQgUm9v +dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMjUwNzI5MjEwMDI0WhcNMjcwNzI5 +MjEwMDI0WjBvMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQG +A1UEBwwNU2FuIEZyYW5jaXNjbzEPMA0GA1UECgwGQmFkU1NMMSIwIAYDVQQDDBlC +YWRTU0wgQ2xpZW50IENlcnRpZmljYXRlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAxzdfEeseTs/rukjly6MSLHM+Rh0enA3Ai4Mj2sdl31x3SbPoen08 +utVhjPmlxIUdkiMG4+ffe7N+JtDLG75CaxZp9CxytX7kywooRBJsRnQhmQPca8MR +WAJBIz+w/L+3AFkTIqWBfyT+1VO8TVKPkEpGdLDovZOmzZAASi9/sj+j6gM7AaCi +DeZTf2ES66abA5pOp60Q6OEdwg/vCUJfarhKDpi9tj3P6qToy9Y4DiBUhOct4MG8 +w5XwmKAC+Vfm8tb7tMiUoU0yvKKOcL6YXBXxB2kPcOYxYNobXavfVBEdwSrjQ7i/ +s3o6hkGQlm9F7JPEuVgbl/Jdwa64OYIqjQIDAQABoy0wKzAJBgNVHRMEAjAAMBEG +CWCGSAGG+EIBAQQEAwIHgDALBgNVHQ8EBAMCBeAwDQYJKoZIhvcNAQELBQADggIB +AFbvDQYvhrsChovOvkFxiiS0YFIuROlCdDkD3wFv+r14BbPDOjsryI2y1vipg6QR +DEE9lGin0SwkKH0zQ+mKUnGBafmuZNZSonqzCmfOG9BpiO8Th5UiPksxhNRUSF+N +GvXLYf2o1syDxXfAGrHO93DgFJiok9rCWRCIESz63ss42rhmDpQyrjBUPLF0Rs8i +dgVyBNz9ZaxmE7cio8IXQ/ETnWuOUsg/Avn8Qd/XWasALQOpf6l5PcfuhT3F28ED +sNUxMZaHJoDAVblX/Sf887sz1kyk/gyOeMKTc80n+J9wrA21Q4ik21x0oY8g3iyT +DsnM3brzkK5tl1kyrU/arC/lOA/jXa8VjZJV1L4H9LWH0IVxk1TjCR/9CSe5bFnX +2iL9QVRLQK4sFJVIcQmreLBgSKI22rkSpNPV+ColvpaKOxD5w5u8w7pK4H2tqXXB +udlIQjhYiQeVYbimyzJpNhivFNzW7Lexr2DO4+KcqqI1Nfsodn8KZgfk+kUI7HQq +KHl16ouJZaALFLkCXB3hv9pjKLdLMBpUpJ/f7FwmF295ORsDx5G0FzBSlz1p4/GH +VUrb3c9pDlOm/nZPljhU1NZgcoX8FtMs+KBFoCvxJVTimRzRT6pC3SWkRvCGVkpi +acYqAN2nQAMl02YSo/bG1YO7iCjuaMa2ZYq7YPTRTkJP +-----END CERTIFICATE----- +Bag Attributes + localKeyID: E2 03 61 76 14 F3 BF 5C E2 23 B3 3A 02 65 D6 D7 4E 36 13 2B +Key Attributes: +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIi20ZhrJrP60CAggA +MBQGCCqGSIb3DQMHBAgEtb/4tSCJ0wSCBMgUpsV6jT+WecFLqmpiigkGvpqjcq+L +NICe29dInPbuU4Wm/SHi+NiDm/zuGASR3Mg5+YWlRGO1kum8NkTi957oEvIkXV5V +SV/ys8zyA88McNIS/7THIm1s3BibhmawpdvM8TuDUMi2ECkqftSLXCRbu5VThJIx ++v2bkEXQcv/UMxa5H0GZkA2COr3mDj/IipcWpofPxfCsW+L/rnuUakLbUkXOx4+b +AkRBWklqcpFugsKYOIk44iprRYwkGcTtwc2bPUrsi4cIZ6ZoEm5Jq/9rf+BsAQGa +CK2O/cvtQTKp26lKOXoObKjfxvxmTHIQd0xJqfxbJVZVH9ALOpksXocXOmM/r1k6 +Neztuzuh+obGSutbwyZzDS6CDQ9ocEKD1a6q9xEdLGU5X+TqywCCthwp8g0RSTvn +fYg14bV+gqt+1UMeheyvpOHlMMDTl5PBMwDuWdXkdfc3Vdd09yl5LBbcmrkrd5vQ +5q3Aoedz9sPLTHVPVqkDAUfHGrsZdpPzc0yX2MPpYnIXRM057J7G047plV9j/2gK +SlvbRbuQWc2tc+5e9ohhsTS+gKP+RC200B67qEjQKNd6Es3olWWOI5cSUlugni/F +aJTSVf6IOkpmoLk+oc/mfyCAeKABTHukq5fX4ZhSTaki4cdVgNKX97qvpI3++Jqu +D+SQXjSLrvWjzeiP8a5CoJVoTU/HYppwZeuyncSCtT2K0lGb0mPZOclwglrealPw +G7I2qsSLTQQHwcanRNYrEN69q9L2pij/TUSaiRGMKqracRRD3NG24n2+alRi7CUE +gTbsAQ2MjnQ5PdHLAUXJQEquWZ1u0870+PUd9EmH9ltszNZphHkjirjpXiIaeq8e +YvNUTkyIKDquOR8BydwM3igWROnTDCh+gu9FtA8Q/Ku12RytIihTSZYIrlCsIaL6 +lOB6aMU8KJPK7TAx0ekAbzC1h0oI70+zX7aQTVsceMGlasAK1DxQPkRF22zqvU7Y +gGRm/LacM7ky3DZQW3Go3nmrDcbMKioAOtDNn003IH3aVhns92irG2q5SdhT0lB4 +tySV8xjqge6wbDn/W4r2IOrrBW/WzqKxuerO+cP5vk4po/uvmIyGQHPj+6jrXI+6 ++p6boGs0v7dKk9SjcRhPbpLyQvFWv+f2RVDnapCeEIuo6NKHPYvX0gEbUd1y7wLU +87v56jW1TK7RikIv/3MNMx4f24no7G+v/VyeB8IY+/VQ3YP4uRZ5j7LVkSkMbBK+ +rm74CnkcjNLt5+P6m1DFfT+YvjJItBdJ59w//6yu5kDNw4q2SDjSETmakWlpamr8 +9//9PzxUaZ92Gcs9t0c7vP1wEmVUWiuXxfTP7GnzKotmahNgU3V5hZRmdLJPymJm +P3D/ocEw2TUaQBU7ajct5WWijGaAQqHDVsNqb0lqgg8y2hGs1cw8kPZx3L7j6c8Q +svpcYCizklfv1dXwacKSZrNUsiv6pUUTntIpIZBWXVfib1OKi7IRV1KThui0CYLf +bukPbh8FXiFu75zxLtU4HWWT+R3QRhvEsb0RHtrpwg1Fa+kIyVBvV6TcjMD0uSnu ++BVirHsYjBJ/Ucj7ftZQoqN3yjTxUYgVMn2Nwd/Qb0slq1+wc+VxK+OUba3J+z6J +/xE= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/src/test/resources/image.jpg b/unirest-bdd-tests/src/test/resources/image.jpg similarity index 100% rename from src/test/resources/image.jpg rename to unirest-bdd-tests/src/test/resources/image.jpg diff --git a/unirest-bdd-tests/src/test/resources/public/data/cp1250.txt b/unirest-bdd-tests/src/test/resources/public/data/cp1250.txt new file mode 100644 index 000000000..36be2aa8b --- /dev/null +++ b/unirest-bdd-tests/src/test/resources/public/data/cp1250.txt @@ -0,0 +1 @@ +šžęáíé \ No newline at end of file diff --git a/unirest-bdd-tests/src/test/resources/rawPost.txt b/unirest-bdd-tests/src/test/resources/rawPost.txt new file mode 100644 index 000000000..42b11480a --- /dev/null +++ b/unirest-bdd-tests/src/test/resources/rawPost.txt @@ -0,0 +1,16 @@ +--IDENTIFIER +Content-Disposition: form-data; name="marky" +Content-Type: application/x-www-form-urlencoded; charset=UTF-8 + +mark +--IDENTIFIER +Content-Disposition: form-data; name="funky" +Content-Type: application/x-www-form-urlencoded; charset=UTF-8 + +bunch +--IDENTIFIER +Content-Disposition: form-data; name="file"; filename="test.txt" +Content-Type: application/octet-stream + +This is a test file +--IDENTIFIER-- diff --git a/unirest-bdd-tests/src/test/resources/rawPostSorted.txt b/unirest-bdd-tests/src/test/resources/rawPostSorted.txt new file mode 100644 index 000000000..400079d56 --- /dev/null +++ b/unirest-bdd-tests/src/test/resources/rawPostSorted.txt @@ -0,0 +1,16 @@ +--IDENTIFIER +Content-Disposition: form-data; name="file"; filename="test.txt" +Content-Type: application/octet-stream + +This is a test file +--IDENTIFIER +Content-Disposition: form-data; name="funky" +Content-Type: application/x-www-form-urlencoded; charset=UTF-8 + +bunch +--IDENTIFIER +Content-Disposition: form-data; name="marky" +Content-Type: application/x-www-form-urlencoded; charset=UTF-8 + +mark +--IDENTIFIER-- diff --git a/unirest-bdd-tests/src/test/resources/spidey.jpg b/unirest-bdd-tests/src/test/resources/spidey.jpg new file mode 100644 index 000000000..ecc759109 Binary files /dev/null and b/unirest-bdd-tests/src/test/resources/spidey.jpg differ diff --git a/unirest-bdd-tests/src/test/resources/spidey.pdf b/unirest-bdd-tests/src/test/resources/spidey.pdf new file mode 100644 index 000000000..c4d309571 Binary files /dev/null and b/unirest-bdd-tests/src/test/resources/spidey.pdf differ diff --git a/unirest-bdd-tests/src/test/resources/test-json-patch.json b/unirest-bdd-tests/src/test/resources/test-json-patch.json new file mode 100644 index 000000000..6882fc6e2 --- /dev/null +++ b/unirest-bdd-tests/src/test/resources/test-json-patch.json @@ -0,0 +1,8 @@ +[ + {"op":"add","path":"/fruits/-","value":"Apple"}, + {"op":"remove","path":"/bugs"}, + {"op":"replace","path":"/lastname","value":"Flintstone"}, + {"op":"test","path":"/firstname","value":"Fred"}, + {"op":"move","path":"/new/location","from":"/old/location"}, + {"op":"copy","path":"/new/location","from":"/original/location"} +] \ No newline at end of file diff --git a/src/test/resources/test b/unirest-bdd-tests/src/test/resources/test.txt similarity index 100% rename from src/test/resources/test rename to unirest-bdd-tests/src/test/resources/test.txt diff --git a/unirest-bdd-tests/src/test/resources/test2.txt b/unirest-bdd-tests/src/test/resources/test2.txt new file mode 100644 index 000000000..9e7eb5bf8 --- /dev/null +++ b/unirest-bdd-tests/src/test/resources/test2.txt @@ -0,0 +1 @@ +this is another test \ No newline at end of file diff --git a/unirest-java-bom/pom.xml b/unirest-java-bom/pom.xml new file mode 100644 index 000000000..0e56ab0ae --- /dev/null +++ b/unirest-java-bom/pom.xml @@ -0,0 +1,49 @@ + + + + unirest-java-parent + com.konghq + 4.10.3-SNAPSHOT + + 4.0.0 + unirest-java-bom + unirest-java-bom + pom + + + ${project.parent.basedir} + 11 + 11 + + + + + + com.konghq + unirest-java-core + ${project.version} + + + com.konghq + unirest-modules-jackson + ${project.version} + + + com.konghq + unirest-modules-jackson-legacy + ${project.version} + + + com.konghq + unirest-modules-gson + ${project.version} + + + com.konghq + unirest-modules-mocks + ${project.version} + test + + + + \ No newline at end of file diff --git a/unirest-modules-gson/README.md b/unirest-modules-gson/README.md new file mode 100644 index 000000000..70687b3a3 --- /dev/null +++ b/unirest-modules-gson/README.md @@ -0,0 +1,19 @@ +# Gson Object Mapper for Unirest + +Welcome. This is a Object Mapper for Unirest based on the popular google Gson JSON parser. +Use it like this: + +```java + Unirest.config().setObjectMapper(new GsonObjectMapper()); +``` + +You may also provide it with your own Gson instance. + +## Install With [Maven](https://mvnrepository.com/artifact/com.konghq/unirest-object-mappers-gson) +``` + + com.konghq + unirest-object-mappers-gson + 2.0.00 + +``` \ No newline at end of file diff --git a/unirest-modules-gson/pom.xml b/unirest-modules-gson/pom.xml new file mode 100644 index 000000000..4389e04cf --- /dev/null +++ b/unirest-modules-gson/pom.xml @@ -0,0 +1,33 @@ + + + 4.0.0 + + + com.konghq + unirest-java-parent + 4.10.3-SNAPSHOT + + + unirest-modules-gson + unirest-modules-gson + Gson based object mapper for Unirest + jar + + + ${project.parent.basedir} + + + + + com.konghq + unirest-java-core + ${project.version} + provided + + + com.google.code.gson + gson + 2.13.2 + + + \ No newline at end of file diff --git a/unirest-modules-gson/src/main/java/kong/unirest/modules/gson/GsonArray.java b/unirest-modules-gson/src/main/java/kong/unirest/modules/gson/GsonArray.java new file mode 100644 index 000000000..d45c858dd --- /dev/null +++ b/unirest-modules-gson/src/main/java/kong/unirest/modules/gson/GsonArray.java @@ -0,0 +1,118 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.modules.gson; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonPrimitive; +import kong.unirest.core.json.JsonEngine; + +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +class GsonArray extends GsonElement implements JsonEngine.Array { + GsonArray(JsonArray jsonElement) { + super(jsonElement); + } + + GsonArray() { + this(new JsonArray()); + } + + @Override + public int size() { + return element.size(); + } + + @Override + public JsonEngine.Element get(int index) { + return GsonEngine.toElement(element.get(index)); + } + + @Override + public void add(JsonEngine.Element obj) { + if(obj == null){ + element.add(JsonNull.INSTANCE); + } else { + element.add((JsonElement) obj.getEngineElement()); + } + } + + @Override + public void set(int index, JsonEngine.Element o) { + element.set(index, o == null ? JsonNull.INSTANCE : o.getEngineElement()); + } + + @Override + public void add(Number num) { + element.add(num); + } + + @Override + public void add(String str) { + element.add(str); + } + + @Override + public void add(Boolean bool) { + element.add(bool); + } + + @Override + public String join(String token) { + return StreamSupport.stream(element.spliterator(), false) + .map(String::valueOf) + .collect(Collectors.joining(token)); + } + + @Override + public JsonEngine.Array put(int index, Number number) { + element.set(index, new JsonPrimitive(number)); + return this; + } + + @Override + public JsonEngine.Element put(int index, String str) { + element.set(index, new JsonPrimitive(str)); + return this; + } + + @Override + public JsonEngine.Element put(int index, Boolean number) { + element.set(index, new JsonPrimitive(number)); + return this; + } + + + @Override + public JsonEngine.Element remove(int index) { + return GsonEngine.toElement(element.remove(index)); + } + + + +} diff --git a/unirest-modules-gson/src/main/java/kong/unirest/modules/gson/GsonElement.java b/unirest-modules-gson/src/main/java/kong/unirest/modules/gson/GsonElement.java new file mode 100644 index 000000000..c7fecdce7 --- /dev/null +++ b/unirest-modules-gson/src/main/java/kong/unirest/modules/gson/GsonElement.java @@ -0,0 +1,139 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.modules.gson; + +import com.google.gson.*; +import kong.unirest.core.json.*; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Objects; + +class GsonElement implements JsonEngine.Element { + protected T element; + + GsonElement(T element) { + this.element = element; + } + + @Override + public JsonEngine.Object getAsJsonObject() { + return new GsonObject(element.getAsJsonObject()); + } + + @Override + public boolean isJsonNull() { + return element.isJsonNull(); + } + + @Override + public JsonEngine.Primitive getAsJsonPrimitive() { + return new GsonPrimitive(element.getAsJsonPrimitive()); + } + + @Override + public JsonEngine.Array getAsJsonArray() { + return new GsonArray(element.getAsJsonArray()); + } + + @Override + public float getAsFloat() { + return element.getAsFloat(); + } + + @Override + public double getAsDouble() { + return element.getAsDouble(); + } + + @Override + public String getAsString() { + return element.getAsString(); + } + + @Override + public long getAsLong() { + return element.getAsLong(); + } + + @Override + public int getAsInt() { + return element.getAsInt(); + } + + @Override + public boolean getAsBoolean() { + return element.getAsBoolean(); + } + + @Override + public BigInteger getAsBigInteger() { + return element.getAsBigInteger(); + } + + @Override + public BigDecimal getAsBigDecimal() { + return element.getAsBigDecimal(); + } + + @Override + public boolean isJsonPrimitive() { + return element.isJsonPrimitive(); + } + + @Override + public JsonEngine.Primitive getAsPrimitive() { + return new GsonPrimitive(element.getAsJsonPrimitive()); + } + + @Override + public boolean isJsonArray() { + return element.isJsonArray(); + } + + @Override + public boolean isJsonObject() { + return element.isJsonObject(); + } + + @Override + public T getEngineElement() { + return (T)element; + } + + @Override + public boolean equals(Object o) { + if (this == o) {return true;} + if (o == null || getClass() != o.getClass()) {return false;} + GsonElement that = (GsonElement) o; + return Objects.equals(element, that.element); + } + + @Override + public int hashCode() { + return Objects.hash(element); + } +} diff --git a/unirest-modules-gson/src/main/java/kong/unirest/modules/gson/GsonEngine.java b/unirest-modules-gson/src/main/java/kong/unirest/modules/gson/GsonEngine.java new file mode 100644 index 000000000..0a5cb2711 --- /dev/null +++ b/unirest-modules-gson/src/main/java/kong/unirest/modules/gson/GsonEngine.java @@ -0,0 +1,267 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.modules.gson; + +import com.google.gson.*; +import com.google.gson.internal.LinkedTreeMap; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; +import kong.unirest.core.ObjectMapper; +import kong.unirest.core.json.*; + +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +public class GsonEngine implements JsonEngine { + private static final TypeAdapter ADAPTER = new JavaTypeAdapter(); + private Gson gson; + private Gson pretty; + + + + public GsonEngine() { + GsonBuilder builder = new GsonBuilder() + .disableHtmlEscaping() + .serializeNulls() + .registerTypeAdapter(Map.class, ADAPTER) + .registerTypeAdapter(List.class, ADAPTER); + gson = builder.create(); + pretty = builder.setPrettyPrinting().create(); + } + + static Element toElement(JsonElement jsonElement) { + if (jsonElement instanceof JsonObject){ + return new GsonObject((JsonObject) jsonElement); + } else if (jsonElement instanceof JsonArray) { + return new GsonArray((JsonArray) jsonElement); + } else if (jsonElement instanceof JsonPrimitive) { + return new GsonPrimitive((JsonPrimitive) jsonElement); + } + return new GsonNull(); + } + + @Override + public void toJson(Element obj, Writer sw) { + gson.toJson(obj.getEngineElement(), sw); + } + + @Override + public void toPrettyJson(Element obj, Writer sw) { + pretty.toJson(obj.getEngineElement(), sw); + } + + @Override + public String toPrettyJson(Element obj) { + if(obj instanceof Element){ + return pretty.toJson(((Element)obj).getEngineElement()); + } + return pretty.toJson(obj); + } + + + @Override + public String toJson(Element obj) { + if(obj instanceof Element){ + return gson.toJson(((Element)obj).getEngineElement()); + } + return gson.toJson(obj); + } + + @Override + public Element toJsonTree(java.lang.Object obj) { + return toElement(gson.toJsonTree(obj)); + } + + + @Override + public Object newEngineObject() { + return new GsonObject(); + } + + @Override + public Object newEngineObject(String string) { + try { + JsonObject element = gson.fromJson(string, JsonObject.class); + return new GsonObject(element); + }catch (JsonSyntaxException e){ + throw new JSONException("Invalid JSON"); + } + } + + @Override + public Array newEngineArray() { + return new GsonArray(); + } + + @Override + public T fromJson(Element obj, Class mapClass) { + return gson.fromJson((JsonElement) obj.getEngineElement(), mapClass); + } + + @Override + public Array newJsonArray(String jsonString) { + try { + return new GsonArray(gson.fromJson(jsonString, JsonArray.class)); + }catch (JsonSyntaxException e){ + throw new JSONException("Invalid JSON"); + } + } + + @Override + public Array newJsonArray(Collection collection) { + GsonArray a = new GsonArray(); + for(java.lang.Object o : collection){ + add(a, o); + } + return a; + } + + private void add(GsonArray a, java.lang.Object o) { + if(o instanceof Number){ + a.add((Number) o); + } else if (o instanceof String) { + a.add((String) o); + }else if (o instanceof Boolean) { + a.add((Boolean) o); + }else if(o instanceof JSONElement) { + a.add(((JSONElement) o).getElement()); + } else if(o instanceof Element) { + a.add((Element) o); + } else { + JsonElement tree = gson.toJsonTree(o); + a.add(toElement(tree)); + } + } + + @Override + public Primitive newJsonPrimitive(T enumValue) { + if(enumValue == null){ + return new GsonNull(); + } else { + return new GsonPrimitive(new JsonPrimitive(enumValue.name())); + } + } + + @Override + public Primitive newJsonPrimitive(String string) { + if(string == null){ + return new GsonNull(); + } else { + return new GsonPrimitive(new JsonPrimitive(string)); + } + } + + @Override + public Primitive newJsonPrimitive(Number number) { + if(number == null){ + return new GsonNull(); + } else { + return new GsonPrimitive(new JsonPrimitive(number)); + } + } + + @Override + public Primitive newJsonPrimitive(Boolean bool) { + if(bool == null){ + return new GsonNull(); + } else { + return new GsonPrimitive(new JsonPrimitive(bool)); + } + } + + @Override + public ObjectMapper getObjectMapper() { + return new GsonObjectMapper(); + } + + @Override + public String quote(java.lang.Object s) { + return gson.toJson(s); + } + + static class JavaTypeAdapter extends TypeAdapter { + + private final TypeAdapter delegate = new Gson().getAdapter(java.lang.Object.class); + + @Override + public void write(JsonWriter out, java.lang.Object value) throws IOException { + delegate.write(out, value); + } + + @Override + public java.lang.Object read(JsonReader in) throws IOException { + JsonToken token = in.peek(); + switch (token) { + case BEGIN_ARRAY: + List list = new ArrayList<>(); + in.beginArray(); + while (in.hasNext()) { + list.add(read(in)); + } + in.endArray(); + return list; + + case BEGIN_OBJECT: + Map map = new LinkedTreeMap<>(); + in.beginObject(); + while (in.hasNext()) { + map.put(in.nextName(), read(in)); + } + in.endObject(); + return map; + + case STRING: + return in.nextString(); + + case NUMBER: + String n = in.nextString(); + if (n.indexOf('.') != -1) { + return Double.parseDouble(n); + } + long l = Long.parseLong(n); + if(l < Integer.MAX_VALUE){ + return (int)l; + } + return l; + + case BOOLEAN: + return in.nextBoolean(); + + case NULL: + in.nextNull(); + return null; + + default: + throw new IllegalStateException(); + } + } + } +} diff --git a/unirest-modules-gson/src/main/java/kong/unirest/modules/gson/GsonNull.java b/unirest-modules-gson/src/main/java/kong/unirest/modules/gson/GsonNull.java new file mode 100644 index 000000000..91e589395 --- /dev/null +++ b/unirest-modules-gson/src/main/java/kong/unirest/modules/gson/GsonNull.java @@ -0,0 +1,45 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.modules.gson; + +import com.google.gson.JsonNull; +import kong.unirest.core.json.JsonEngine; + +public class GsonNull extends GsonElement implements JsonEngine.Primitive { + GsonNull() { + super(JsonNull.INSTANCE); + } + + @Override + public boolean isBoolean() { + return false; + } + + @Override + public boolean isNumber() { + return false; + } +} diff --git a/unirest-modules-gson/src/main/java/kong/unirest/modules/gson/GsonObject.java b/unirest-modules-gson/src/main/java/kong/unirest/modules/gson/GsonObject.java new file mode 100644 index 000000000..7345025c6 --- /dev/null +++ b/unirest-modules-gson/src/main/java/kong/unirest/modules/gson/GsonObject.java @@ -0,0 +1,98 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.modules.gson; + +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import kong.unirest.core.json.JsonEngine; + +import java.util.Set; + +public class GsonObject extends GsonElement implements JsonEngine.Object { + GsonObject(JsonObject element) { + super(element); + } + + public GsonObject() { + this(new JsonObject()); + } + + @Override + public int size() { + return element.size(); + } + + @Override + public boolean has(String key) { + return element.has(key); + } + + @Override + public JsonEngine.Element get(String key) { + return GsonEngine.toElement(element.get(key)); + } + + @Override + public void add(String key, JsonEngine.Element value) { + element.add(key, value.getEngineElement()); + } + + @Override + public void addProperty(String key, Boolean value) { + element.addProperty(key, value); + } + + @Override + public void addProperty(String key, String value) { + element.addProperty(key, value); + } + + @Override + public void addProperty(String key, Number value) { + element.addProperty(key, value); + } + + @Override + public void addProperty(String key, JsonEngine.Element value) { +// element.addProperty(key, value.getEngineElement()); + } + + @Override + public void remove(String key) { + element.remove(key); + } + + @Override + public void add(String key, E enumvalue) { + element.add(key, enumvalue == null ? JsonNull.INSTANCE : new JsonPrimitive(enumvalue.name())); + } + + @Override + public Set keySet() { + return element.keySet(); + } +} diff --git a/unirest-modules-gson/src/main/java/kong/unirest/modules/gson/GsonObjectMapper.java b/unirest-modules-gson/src/main/java/kong/unirest/modules/gson/GsonObjectMapper.java new file mode 100644 index 000000000..9f7282a09 --- /dev/null +++ b/unirest-modules-gson/src/main/java/kong/unirest/modules/gson/GsonObjectMapper.java @@ -0,0 +1,244 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* +The MIT License + +Copyright for portions of unirest-java are held by Kong Inc (c) 2018. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +package kong.unirest.modules.gson; + +import com.google.gson.*; +import kong.unirest.core.GenericType; +import kong.unirest.core.ObjectMapper; +import kong.unirest.core.UnirestException; + +import java.lang.reflect.Type; +import java.time.*; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoField; +import java.time.temporal.TemporalAccessor; +import java.util.*; + +public class GsonObjectMapper implements ObjectMapper { + + public static final String ISO_8601 = "yyyy-MM-dd'T'HH:mm'Z'"; + private Gson gson; + + public GsonObjectMapper() { + + } + + public GsonObjectMapper(Gson gson) { + this.gson = gson; + } + + @Override + public T readValue(String value, Class valueType) { + return getGson().fromJson(value, valueType); + } + + @Override + public T readValue(String value, GenericType genericType) { + return getGson().fromJson(value, genericType.getType()); + } + + @Override + public String writeValue(Object value) { + return getGson().toJson(value); + } + + private Gson getGson() { + if (gson == null) { + gson = new GsonBuilder() + .setDateFormat(ISO_8601) + .disableHtmlEscaping() + .registerTypeHierarchyAdapter(Calendar.class, new CalendarSerializer()) + .registerTypeHierarchyAdapter(ZonedDateTime.class, new ZonedDateAdapter()) + .registerTypeAdapter(Date.class, new DateAdapter()) + .registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter()) + .registerTypeAdapter(LocalDate.class, new LocalDateAdapter()) + .create(); + + } + return gson; + } + + private abstract static class DateTimeAdapter implements JsonSerializer, JsonDeserializer { + static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_OFFSET_DATE_TIME; + static final List FORMATS = Arrays.asList( + DateTimeFormatter.ISO_OFFSET_DATE_TIME, + DateTimeFormatter.ISO_DATE, + DateTimeFormatter.ISO_LOCAL_DATE_TIME + ); + + abstract T from(Long millies); + abstract T from(TemporalAccessor dt); + + @Override + public T deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { + if(jsonElement.isJsonPrimitive()) { + JsonPrimitive prim = jsonElement.getAsJsonPrimitive(); + if(prim.isNumber()){ + return from(prim.getAsLong()); + } else if (prim.isString()){ + String asString = prim.getAsString(); + for (DateTimeFormatter format : FORMATS) { + try { + TemporalAccessor dt = format.parse(asString); + return from(dt); + } catch (Exception ignored) { + //System.out.println("ignored = " + ignored); + } + } + } + } else if (jsonElement.isJsonNull()){ + return null; + } + throw new UnirestException(String.format("Could Not Parse as %s: %s", type.getTypeName(), jsonElement.getAsString())); + } + + @Override + public JsonElement serialize(T dateObj, Type type, JsonSerializationContext jsonSerializationContext) { + return new JsonPrimitive(dateObj.toString()); + } + } + + private static class CalendarSerializer extends DateTimeAdapter { + @Override + public JsonElement serialize(Calendar calendar, Type type, JsonSerializationContext jsonSerializationContext) { + GregorianCalendar gCal = (GregorianCalendar) calendar; + String dateAsString = FORMATTER.format(gCal.toZonedDateTime()); + return jsonSerializationContext.serialize(dateAsString); + } + + @Override + Calendar from(Long millies) { + return GregorianCalendar.from(ZonedDateTime.ofInstant(Instant.ofEpochMilli(millies), ZoneId.systemDefault())); + } + + @Override + Calendar from(TemporalAccessor dt) { + return GregorianCalendar.from(toZonedInstance(dt)); + } + + private ZonedDateTime toZonedInstance(TemporalAccessor dt) { + if(dt.isSupported(ChronoField.HOUR_OF_DAY)){ + return ZonedDateTime.from(dt); + } else { + return LocalDate.from(dt).atStartOfDay(ZoneId.systemDefault()); + } + } + } + + private static class DateAdapter extends DateTimeAdapter { + @Override + Date from(Long millies) { + return new Date(millies); + } + + @Override + Date from(TemporalAccessor dt) { + return Date.from(toInstant(dt)); + } + + private Instant toInstant(TemporalAccessor dt){ + if(dt.isSupported(ChronoField.HOUR_OF_DAY)){ + return LocalDateTime.from(dt).atZone(ZoneId.systemDefault()).toInstant(); + } else { + return LocalDate.from(dt).atStartOfDay(ZoneId.systemDefault()).toInstant(); + } + } + + @Override + public JsonElement serialize(Date date, Type type, JsonSerializationContext jsonSerializationContext) { + return new JsonPrimitive(FORMATTER.format(ZonedDateTime.from(date.toInstant().atZone(ZoneId.systemDefault())))); + } + + } + + private static class ZonedDateAdapter extends DateTimeAdapter { + @Override + ZonedDateTime from(Long millies) { + return ZonedDateTime.ofInstant(Instant.ofEpochMilli(millies), ZoneId.systemDefault()); + } + + @Override + ZonedDateTime from(TemporalAccessor dt) { + if(dt.isSupported(ChronoField.HOUR_OF_DAY)){ + return ZonedDateTime.from(dt); + } else { + return LocalDateTime.from(dt).atZone(ZoneId.systemDefault()); + } + } + } + + private static class LocalDateTimeAdapter extends DateTimeAdapter { + @Override + LocalDateTime from(Long millies) { + return LocalDateTime.ofInstant(Instant.ofEpochMilli(millies), ZoneId.systemDefault()); + } + + @Override + LocalDateTime from(TemporalAccessor dt) { + if(dt.isSupported(ChronoField.HOUR_OF_DAY)){ + return LocalDateTime.from(dt); + } else { + return LocalDate.from(dt).atStartOfDay(); + } + } + } + + private static class LocalDateAdapter extends DateTimeAdapter { + @Override + LocalDate from(Long millies) { + return LocalDateTime.ofInstant(Instant.ofEpochMilli(millies), ZoneId.systemDefault()).toLocalDate(); + } + + @Override + LocalDate from(TemporalAccessor dt) { + return LocalDate.from(dt); + } + } +} diff --git a/unirest-modules-gson/src/main/java/kong/unirest/modules/gson/GsonPrimitive.java b/unirest-modules-gson/src/main/java/kong/unirest/modules/gson/GsonPrimitive.java new file mode 100644 index 000000000..a7eb7c01f --- /dev/null +++ b/unirest-modules-gson/src/main/java/kong/unirest/modules/gson/GsonPrimitive.java @@ -0,0 +1,46 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.modules.gson; + +import com.google.gson.JsonPrimitive; +import kong.unirest.core.json.JsonEngine; + +class GsonPrimitive extends GsonElement implements JsonEngine.Primitive { + + GsonPrimitive(JsonPrimitive valueType) { + super(valueType); + } + + @Override + public boolean isBoolean() { + return element.isBoolean(); + } + + @Override + public boolean isNumber() { + return element.isNumber(); + } +} diff --git a/unirest-modules-gson/src/main/resources/META-INF/services/kong.unirest.core.json.JsonEngine b/unirest-modules-gson/src/main/resources/META-INF/services/kong.unirest.core.json.JsonEngine new file mode 100644 index 000000000..2e762f0c5 --- /dev/null +++ b/unirest-modules-gson/src/main/resources/META-INF/services/kong.unirest.core.json.JsonEngine @@ -0,0 +1 @@ +kong.unirest.modules.gson.GsonEngine \ No newline at end of file diff --git a/unirest-modules-gson/src/test/java/kong/unirest/modules/gson/Foo.java b/unirest-modules-gson/src/test/java/kong/unirest/modules/gson/Foo.java new file mode 100644 index 000000000..467db2348 --- /dev/null +++ b/unirest-modules-gson/src/test/java/kong/unirest/modules/gson/Foo.java @@ -0,0 +1,60 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.modules.gson; + + + +import java.util.Objects; + +public class Foo { + public String bar; + + public Foo(){ } + + public Foo(String bar) { + this.bar = bar; + } + + @Override + public String toString() { + return "Foo{" + + "bar='" + bar + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) {return true;} + if (o == null || getClass() != o.getClass()) {return false;} + Foo foo = (Foo) o; + return Objects.equals(bar, foo.bar); + } + + @Override + public int hashCode() { + return Objects.hash(bar); + } +} \ No newline at end of file diff --git a/unirest-modules-gson/src/test/java/kong/unirest/modules/gson/GsonCoreFactoryTest.java b/unirest-modules-gson/src/test/java/kong/unirest/modules/gson/GsonCoreFactoryTest.java new file mode 100644 index 000000000..b5d2bd4c0 --- /dev/null +++ b/unirest-modules-gson/src/test/java/kong/unirest/modules/gson/GsonCoreFactoryTest.java @@ -0,0 +1,51 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.modules.gson; + +import kong.unirest.core.json.CoreFactory; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class GsonCoreFactoryTest { + @Test + void canLoadByServiceLocator() { + assertThat(CoreFactory.findEngineWithServiceLocator()) + .isInstanceOf(GsonEngine.class); + } + + @Test + void canLoadByKnownClassNames() { + assertThat(CoreFactory.findEngineWithClassLoader()) + .isInstanceOf(GsonEngine.class); + } + + @Test + void willLoadOneWaOrAnother() { + assertThat(CoreFactory.findEngine()) + .isInstanceOf(GsonEngine.class); + } +} diff --git a/unirest-modules-gson/src/test/java/kong/unirest/modules/gson/GsonObjectMapperTest.java b/unirest-modules-gson/src/test/java/kong/unirest/modules/gson/GsonObjectMapperTest.java new file mode 100644 index 000000000..738075429 --- /dev/null +++ b/unirest-modules-gson/src/test/java/kong/unirest/modules/gson/GsonObjectMapperTest.java @@ -0,0 +1,130 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* +The MIT License + +Copyright for portions of unirest-java are held by Kong Inc (c) 2018. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +package kong.unirest.modules.gson; + +import com.google.gson.GsonBuilder; +import kong.unirest.core.GenericType; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class GsonObjectMapperTest { + private GsonObjectMapper om = new GsonObjectMapper(); + + @Test + void canWrite() throws Exception { + var test = new TestMe("foo", 42, new TestMe("bar", 666, null)); + + String json = om.writeValue(test); + + JSONAssert.assertEquals( + "{\"text\":\"foo\",\"nmbr\":42,\"another\":{\"text\":\"bar\",\"nmbr\":666}}" + , json + , true + ); + } + + @Test + void canRead(){ + var test = om.readValue("{\"text\":\"foo\",\"nmbr\":42,\"another\":{\"text\":\"bar\",\"nmbr\":666}}", + TestMe.class); + + assertEquals("foo", test.text); + assertEquals(42, test.nmbr.intValue()); + assertEquals("bar", test.another.text); + assertEquals(666, test.another.nmbr.intValue()); + assertEquals(null, test.another.another); + } + + @Test + void canReadGenerics(){ + var testList = om.readValue("[{\"text\":\"foo\",\"nmbr\":42,\"another\":{\"text\":\"bar\",\"nmbr\":666,\"another\":null}}]", + new GenericType>(){}); + + var test = testList.get(0); + + assertEquals("foo", test.text); + assertEquals(42, test.nmbr.intValue()); + assertEquals("bar", test.another.text); + assertEquals(666, test.another.nmbr.intValue()); + assertEquals(null, test.another.another); + } + + @Test + void serializeNulls() { + var gson = new GsonBuilder() + .serializeNulls() + .create(); + + om = new GsonObjectMapper(gson); + + TestMe testMe = new TestMe(null, null, null); + + assertEquals("{\"text\":null,\"nmbr\":null,\"another\":null}", om.writeValue(testMe)); + } + + public static class TestMe { + public String text; + public Integer nmbr; + public TestMe another; + + public TestMe(){} + + public TestMe(String text, Integer nmbr, TestMe another) { + this.text = text; + this.nmbr = nmbr; + this.another = another; + } + } +} \ No newline at end of file diff --git a/unirest-modules-gson/src/test/java/kong/unirest/modules/gson/JSONArrayTest.java b/unirest-modules-gson/src/test/java/kong/unirest/modules/gson/JSONArrayTest.java new file mode 100644 index 000000000..443a104a3 --- /dev/null +++ b/unirest-modules-gson/src/test/java/kong/unirest/modules/gson/JSONArrayTest.java @@ -0,0 +1,555 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.modules.gson; + + +import kong.unirest.core.json.JSONArray; +import kong.unirest.core.json.JSONException; +import kong.unirest.core.json.JSONObject; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; + +import java.io.StringWriter; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Collections; + +import static com.google.common.collect.ImmutableMap.of; +import static java.util.Arrays.asList; +import static kong.unirest.modules.gson.JSONObjectTest.assertEqualJson; +import static org.junit.jupiter.api.Assertions.*; + +class JSONArrayTest { + + @Test + void nullForSoManyReasonsWhenZipping() { + var array = new JSONArray(); + assertNull(array.toJSONObject(new JSONArray(Collections.singletonList("foo")))); + array.put(42L); + assertNull(array.toJSONObject(null)); + assertNull(array.toJSONObject(new JSONArray())); + } + + @Test + void serializeNulls() { + var obj = new JSONArray("[1,null]"); + assertEquals("[1,null]", obj.toString()); + } + + @Test + void exeptionWhileZippingForNull() { + var values = new JSONArray(Arrays.asList(1, "foo", false)); + var names = new JSONArray(); + names.put((String)null); + + var ex = assertThrows(JSONException.class, () -> values.toJSONObject(names)); + assertEquals("JSONArray[0] not a string.", ex.getMessage()); + } + + @Test + void zipAnArray() { + var values = new JSONArray(Arrays.asList(1, "foo", false)); + var names = new JSONArray(Arrays.asList("one", "two", "three", "four")); + var zipped = values.toJSONObject(names); + assertEquals(1, zipped.get("one")); + assertEquals("foo", zipped.get("two")); + assertEquals(false, zipped.get("three")); + } + + @Test + void putObject() { + var array = new JSONArray(); + array.put(new Foo("fooooo")); + array.put((Object)"abc"); + array.put((Object)new JSONObject(of("foo", "bar"))); + + assertEquals("Foo{bar='fooooo'}", array.get(0).toString()); + assertEquals("abc", array.get(1)); + assertEquals("{\"foo\":\"bar\"}", array.get(2).toString()); + } + + @Test + void simpleConvert() { + String str = "[{\"foo\": \"bar\"}, {\"baz\": 42}]"; + + var array = new JSONArray(str); + + assertEquals(2, array.length()); + assertEquals("bar", array.getJSONObject(0).getString("foo")); + assertEquals(42, array.getJSONObject(1).getInt("baz")); + } + + @Test + @SuppressWarnings("ConstantConditions") + void putObjectAtElement() { + Object nul = null; + Object num = 42; + Object str = "hi"; + Object bool = true; + Object arr = new JSONArray(asList(1,2,3)); + Object obj = new JSONObject(of("f","b")); + + var array = new JSONArray() + .put(5, obj) + .put(4, arr) + .put(3, bool) + .put(2, str) + .put(1, num) + .put(0, nul); + + assertEquals(nul, array.get(0)); + assertEquals(num, array.get(1)); + assertEquals(str, array.get(2)); + assertEquals(bool, array.get(3)); + assertEquals(arr, array.get(4)); + assertEquals(obj, array.get(5)); + } + + @Test + void numbers() { + var obj = new JSONArray(); + assertSame(obj, obj.put((Number)33)); + obj.put("nan"); + + assertEquals(33, obj.getNumber(0)); + assertNotFound(() -> obj.getNumber(5)); + assertNotType(() -> obj.getNumber(1), "JSONArray[1] is not a number."); + + assertEquals(33, obj.optNumber(0)); + assertEquals(66.6d, obj.optNumber(1, 66.6d)); + assertNull(obj.optNumber(5)); + } + + @Test + void doubles() { + var obj = new JSONArray(); + assertSame(obj, obj.put(33.5d)); + obj.put("nan"); + + assertEquals(33.5d, obj.getDouble(0), 4); + assertNotFound(() -> obj.getDouble(5)); + assertNotType(() -> obj.getDouble(1), "JSONArray[1] is not a number."); + + assertEquals(33.5d, obj.optDouble(0), 4); + assertEquals(66.6d, obj.optDouble(1, 66.6d), 4); + assertEquals(Double.NaN, obj.optDouble(5), 4); + } + + @Test + void floats() { + var obj = new JSONArray(); + assertSame(obj, obj.put(33.5f)); + obj.put("nan"); + + assertEquals(33.5f, obj.getFloat(0), 4); + assertNotFound(() -> obj.getFloat(5)); + assertNotType(() -> obj.getFloat(1), "JSONArray[1] is not a number."); + + assertEquals(33.5f, obj.optFloat(0), 4); + assertEquals(66.6f, obj.optFloat(5, 66.6f), 4); + assertEquals(Float.NaN, obj.optFloat(5), 4); + } + + @Test + void longs() { + var obj = new JSONArray(); + assertSame(obj, obj.put(33L)); + obj.put("nan"); + + assertEquals(33L, obj.getLong(0)); + assertNotFound(() -> obj.getLong(5)); + assertNotType(() -> obj.getLong(1), "JSONArray[1] is not a number."); + + assertEquals(33L, obj.optLong(0)); + assertEquals(66L, obj.optLong(5, 66)); + assertEquals(0L, obj.optLong(5)); + } + + @Test + void bools() { + var obj = new JSONArray(); + assertSame(obj, obj.put(true)); + obj.put("nan"); + + assertTrue(obj.getBoolean(0)); + assertNotFound(() -> obj.getBoolean(5)); + assertNotType(() -> obj.getBoolean(1), "JSONArray[1] is not a boolean."); + + assertTrue(obj.optBoolean(0)); + assertTrue(obj.optBoolean(5, true)); + assertFalse(obj.optBoolean(5)); + } + + @Test + void ints() { + var obj = new JSONArray(); + assertSame(obj, obj.put(33)); + obj.put("nan"); + + assertEquals(33, obj.getInt(0)); + assertNotFound(() -> obj.getInt(5)); + assertNotType(() -> obj.getInt(1), "JSONArray[1] is not a number."); + + assertEquals(33, obj.optInt(0)); + assertEquals(66, obj.optInt(5, 66)); + assertEquals(0, obj.optInt(5)); + } + + @Test + void bigInts() { + var obj = new JSONArray(); + assertSame(obj, obj.put(BigInteger.valueOf(33))); + obj.put("nan"); + + assertEquals(BigInteger.valueOf(33), obj.getBigInteger(0)); + assertNotFound(() -> obj.getBigInteger(5)); + assertNotType(() -> obj.getBigInteger(1), "JSONArray[1] is not a number."); + assertEquals(BigInteger.valueOf(33), obj.optBigInteger(0, BigInteger.TEN)); + assertEquals(BigInteger.TEN, obj.optBigInteger(5, BigInteger.TEN)); + } + + @Test + void bigDecimal() { + var value = BigDecimal.valueOf(33.5); + var obj = new JSONArray(); + assertSame(obj, obj.put(value)); + obj.put("nan"); + + assertEquals(value, obj.getBigDecimal(0)); + assertNotFound(() -> obj.getBigDecimal(5)); + assertNotType(() -> obj.getBigDecimal(1), "JSONArray[1] is not a number."); + assertEquals(value, obj.optBigDecimal(0, BigDecimal.TEN)); + assertEquals(BigDecimal.TEN, obj.optBigDecimal(5, BigDecimal.TEN)); + } + + @Test + void strings() { + var obj = new JSONArray(); + assertSame(obj, obj.put("cheese")); + obj.put(45); + + assertEquals("cheese", obj.getString(0)); + assertNotFound(() -> obj.getString(5)); + assertEquals("45", obj.getString(1)); + assertEquals("cheese", obj.optString(0)); + assertEquals("logs", obj.optString(5, "logs")); + assertEquals("", obj.optString(5)); + } + + @Test + void jsonObjects() throws Exception { + var subObj = new JSONObject("{\"derp\": 42}"); + var obj = new JSONArray(); + assertSame(obj, obj.put(subObj)); + obj.put(45); + + assertEqualJson(subObj, obj.getJSONObject(0)); + assertNotFound(() -> obj.getJSONObject(5)); + assertNotType(() -> obj.getJSONObject(1), "JSONArray[1] is not a JSONObject."); + assertEqualJson(subObj, obj.optJSONObject(0)); + assertNull(obj.optJSONObject(5)); + } + + @Test + void jsonArrays() throws Exception { + var subObj = new JSONArray("[42]"); + var obj = new JSONArray(); + assertSame(obj, obj.put(subObj)); + obj.put(45); + + assertEqualJson(subObj, obj.getJSONArray(0)); + assertNotFound(() -> obj.getJSONArray(5)); + assertNotType(() -> obj.getJSONArray(1), "JSONArray[1] is not a JSONArray."); + assertEqualJson(subObj, obj.optJSONArray(0)); + assertNull(obj.optJSONArray(5)); + } + + @Test + void enums() { + var obj = new JSONArray(); + assertSame(obj, obj.put(fruit.orange)); + obj.put("nan"); + + assertEquals(fruit.orange, obj.getEnum(fruit.class, 0)); + assertNotType(() -> obj.getEnum(fruit.class, 1), "JSONArray[1] is not an enum of type \"fruit\"."); + assertEquals(fruit.orange, obj.optEnum(fruit.class, 0)); + assertEquals(fruit.apple, obj.optEnum(fruit.class, 1, fruit.apple)); + assertNull(obj.optEnum(fruit.class, 5)); + } + + @Test + void joinArray() { + String str = "[33.5, 42, \"foo\", true, apple]"; + + var array = new JSONArray(str); + + assertEquals("33.5, 42, \"foo\", true, \"apple\"", array.join(", ")); + } + + @Test + void toStringIt() { + String str = "[33.5, 42, \"foo\", true, apple]"; + + var array = new JSONArray(str); + + assertEquals("[33.5,42,\"foo\",true,\"apple\"]", array.toString()); + } + + @Test + void toStringItIndent() { + String str = "[33.5, 42, \"foo\", true, apple]"; + + var array = new JSONArray(str); + + assertEquals("[\n" + + " 33.5,\n" + + " 42,\n" + + " \"foo\",\n" + + " true,\n" + + " \"apple\"\n" + + "]", array.toString(3)); + } + + @Test + void rawGet() { + var array = new JSONArray(asList( + 33.457848383, + 1, + "cheese", + new JSONObject(of("foo", "bar")), + new JSONArray(asList(1,2)))); + + assertTrue(array.get(0) instanceof Double); + assertTrue(array.get(1) instanceof Integer); + assertTrue(array.get(2) instanceof String); + assertTrue(array.get(3) instanceof JSONObject); + assertTrue(array.get(4) instanceof JSONArray); + } + + @Test + void arraysOfArrays() { + String str = "[[1,2,3],[6,7,8]]"; + + var array = new JSONArray(str); + + assertEquals(2, array.getJSONArray(0).get(1)); + assertNull(array.optJSONArray(2)); + } + + @Test + void writer() { + String str = "[1,2,3]"; + + var array = new JSONArray(str); + + StringWriter sw = new StringWriter(); + + array.write(sw); + + assertEquals(str, sw.toString()); + } + + @Test + void writerIndent() { + String str = "[1,2,3]"; + + var array = new JSONArray(str); + + var sw = new StringWriter(); + + array.write(sw, 3, 3); + + assertEquals("[\n" + + " 1,\n" + + " 2,\n" + + " 3\n" + + "]", sw.toString()); + } + + @Test + void remove() { + var o = new JSONObject(of("foo","bar")); + var array = new JSONArray(asList(1, o)); + + Object remove = array.remove(1); + assertTrue(remove instanceof JSONObject); + assertEquals(o, remove); + assertEquals(1, array.length()); + assertNull(array.remove(55)); + } + + @Test + void removeMissingIndex() { + var array = new JSONArray("[1,2,3]"); + assertNull(array.remove(55)); + } + + @Test + void putSimple() { + var array = new JSONArray(); + array.put(1); + array.put(Long.MAX_VALUE); + array.put(3.5d); + array.put(6.4f); + array.put("howdy"); + array.put(fruit.pear); + array.put(of("foo", 22)); + array.put(asList(1,2,3)); + + assertEquals(1, array.get(0)); + assertEquals(Long.MAX_VALUE, array.get(1)); + assertEquals(3.5d, array.get(2)); + assertEquals(6.4f, ((Double)array.get(3)).floatValue(), 2); + assertEquals("howdy", array.get(4)); + assertEquals("pear", array.get(5)); + assertTrue(new JSONObject(of("foo", 22)).similar(array.get(6))); + assertTrue(new JSONArray(asList(1,2,3)).similar(array.get(7))); + + assertEquals("[1,9223372036854775807,3.5,6.4,\"howdy\",\"pear\",{\"foo\":22},[1,2,3]]", + array.toString()); + } + + @Test + void putByIndex() { + var array = new JSONArray(); + array.put(5, fruit.pear); + array.put(0, 1); + array.put(1, Long.MAX_VALUE); + array.put(2, 3.5d); + array.put(3, 6.4f); + array.put(4, "howdy"); + array.put(6, of("foo", 22)); + array.put(7, asList(1,2,3)); + + assertEquals(1, array.get(0)); + assertEquals(Long.MAX_VALUE, array.get(1)); + assertEquals(3.5d, array.get(2)); + assertEquals(6.4f, ((Double)array.get(3)).floatValue(), 2); + assertEquals("howdy", array.get(4)); + assertEquals("pear", array.get(5)); + assertTrue(new JSONObject(of("foo", 22)).similar(array.get(6))); + assertTrue(new JSONArray(asList(1,2,3)).similar(array.get(7))); + + assertEquals("[1,9223372036854775807,3.5,6.4,\"howdy\",\"pear\",{\"foo\":22},[1,2,3]]", + array.toString()); + } + + @Test + void query() { + var obj = new JSONArray("[{\"a\":{\"b\": 42}}]"); + assertEquals(42, obj.query("/0/a/b")); + } + + @Test + void putCollection() { + var ints = asList(1, 1, 2, 3); + var array = new JSONArray(); + array.put(ints); + + assertEquals(1, array.length()); + assertEquals(ints, array.getJSONArray(0).toList()); + } + + @Test + void constructCollection() { + var ints = asList(1, 1, 2, 3); + var array = new JSONArray(ints); + + assertEquals(4, array.length()); + assertEquals(ints, array.toList()); + } + + @Test + void constructArray() { + var ints = asList(1, 1, 2, 3); + var array = new JSONArray(ints.toArray()); + + assertEquals(4, array.length()); + assertEquals(ints, array.toList()); + } + + @Test + void constructArrayError() { + var ex = assertThrows(JSONException.class, ()-> new JSONArray(new Object())); + assertEquals("JSONArray initial value should be a string or collection or array.", ex.getMessage()); + } + + @Test + void nullMembers() { + var array = new JSONArray(); + array.put("foo"); + array.put((Object) null); + + assertFalse(array.isNull(0)); + assertTrue(array.isNull(1)); + assertTrue(array.isNull(2)); + assertTrue(array.isNull(33)); + } + + @Test + void puttingSomeRandoObjectWillResultInString() { + var array = new JSONArray(); + array.put(new Fudge()); + + assertEquals("[\"Hello World\"]", array.toString()); + } + + @Test + @SuppressWarnings("SuspiciousMethodCalls") + void iterateOverArray() { + var list = asList(1, 2, 3, 4); + var array = new JSONArray(list); + for(Object i : array){ + assertTrue(list.contains(i)); + } + } + + public static void assertNotType(Executable exRunnable, String message) { + var ex = assertThrows(JSONException.class, exRunnable); + assertEquals(message, ex.getMessage()); + } + + private void assertNotFound(Executable exRunnable) { + assertNotType(exRunnable, "JSONArray[5] not found."); + } + + public static class Fudge { + public String foo = "bar"; + + public String toString(){ + return "Hello World"; + } + } + + public enum fruit {orange, apple, pear} + +} + + + + + diff --git a/unirest-modules-gson/src/test/java/kong/unirest/modules/gson/JSONObjectTest.java b/unirest-modules-gson/src/test/java/kong/unirest/modules/gson/JSONObjectTest.java new file mode 100644 index 000000000..d6d2af58c --- /dev/null +++ b/unirest-modules-gson/src/test/java/kong/unirest/modules/gson/JSONObjectTest.java @@ -0,0 +1,688 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.modules.gson; + + +import kong.unirest.core.UnirestException; +import kong.unirest.core.json.*; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; +import org.skyscreamer.jsonassert.JSONAssert; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Map; +import java.util.Set; + +import static com.google.common.collect.ImmutableMap.of; +import static com.google.common.collect.Sets.newHashSet; +import static java.util.Arrays.asList; +import static org.junit.jupiter.api.Assertions.*; + +class JSONObjectTest { + + @Test + void canSimpleSerializeAnObject() { + var test = new TestMe(true, "Wakk Wakka", 42, new TestMe()); + var o = new JSONObject(test); + assertEquals(true, o.optBoolean("aBoolean")); + assertEquals("Wakk Wakka", o.optString("aSillyString")); + assertEquals(42, o.optNumber("aNumber")); + assertNotNull(o.optJSONObject("aSub")); + } + + @Test + void isEmpty() { + var o = new JSONObject(); + assertTrue(o.isEmpty()); + o.put("foo", "bar"); + assertFalse(o.isEmpty()); + } + + @Test + void isNull() { + var o = new JSONObject(); + assertTrue(o.isNull("foo")); + o.put("foo", (Object)null); + assertTrue(o.isNull("foo")); + o.put("foo", 42); + assertFalse(o.isNull("foo")); + } + + @Test + void contructInvalid() { + JSONException ex = assertThrows(JSONException.class, () -> new JSONObject("foo")); + assertEquals("Invalid JSON", ex.getMessage()); + } + + @Test + void simpleConvert() { + String str = "{\"foo\": {\"baz\": 42}}"; + + JSONObject obj = new JSONObject(str); + + assertTrue(obj.has("foo")); + assertEquals(1, obj.length()); + assertEquals(42, obj.getJSONObject("foo").getInt("baz")); + } + + @Test + void doubles() { + JSONObject obj = new JSONObject(); + obj.put("key", 33.5d); + obj.put("not", "nan"); + + assertEquals(33.5d, obj.getDouble("key"), 4); + assertNotFound(() -> obj.getDouble("boo")); + assertJSONEx(() -> obj.getDouble("not"), "JSONObject[\"not\"] is not a number."); + isTypeAndValue(33.5, Double.class, obj.get("key")); + + assertEquals(33.5d, obj.optDouble("key"), 4); + assertEquals(66.6d, obj.optDouble("boo", 66.6d), 4); + assertEquals(Double.NaN, obj.optDouble("boo"), 4); + } + + @Test + void nullsAreSerialized() { + JSONObject obj = new JSONObject("{\"key1\": \"value\", \"key2\": null}"); + + assertEquals("{\"key1\":\"value\",\"key2\":null}", obj.toString()); + } + + @Test + void issue_366() { + JSONObject jsonObject = new JSONObject("{\"status\":\"OK\",\"message\":\"hive_1597818501335\"}"); + assertEquals("{\"status\":\"OK\",\"message\":\"hive_1597818501335\"}", jsonObject.toString()); + } + + @Test + void nullsAreSerializedOnPretty() { + JSONObject obj = new JSONObject("{\"key1\": \"value\", \"key2\": null}"); + + assertEquals("{\n" + + " \"key1\": \"value\",\n" + + " \"key2\": null\n" + + "}", obj.toString(3)); + } + + @Test + void floats() { + JSONObject obj = new JSONObject(); + obj.put("key", 33.5f); + obj.put("not", "nan"); + + assertEquals(33.5f, obj.getFloat("key"), 4); + assertNotFound(() -> obj.getFloat("boo")); + assertJSONEx(() -> obj.getFloat("not"), "JSONObject[\"not\"] is not a number."); + isTypeAndValue(33.5, Double.class, obj.get("key")); + + assertEquals(33.5f, obj.optFloat("key"), 4); + assertEquals(66.6f, obj.optFloat("boo", 66.6f), 4); + assertEquals(Float.NaN, obj.optFloat("boo"), 4); + } + + @Test + void longs() { + JSONObject obj = new JSONObject(); + obj.put("key", Long.MAX_VALUE); + obj.put("not", "nan"); + + assertEquals(Long.MAX_VALUE, obj.getLong("key")); + assertNotFound(() -> obj.getLong("boo")); + assertJSONEx(() -> obj.getLong("not"), "JSONObject[\"not\"] is not a number."); + isTypeAndValue(Long.MAX_VALUE, Long.class, obj.get("key")); + + assertEquals(Long.MAX_VALUE, obj.optLong("key")); + assertEquals(66L, obj.optLong("boo", 66)); + assertEquals(0L, obj.optLong("boo")); + } + + @Test + void booleans() { + JSONObject obj = new JSONObject(); + obj.put("key", true); + obj.put("not", "nan"); + + assertTrue(obj.getBoolean("key")); + assertNotFound(() -> obj.getBoolean("boo")); + assertJSONEx(() -> obj.getBoolean("not"), "JSONObject[\"not\"] is not a boolean."); + isTypeAndValue(true, Boolean.class, obj.get("key")); + + assertTrue(obj.optBoolean("key")); + assertTrue(obj.optBoolean("boo", true)); + assertFalse(obj.optBoolean("boo")); + } + + @Test + void ints() { + JSONObject obj = new JSONObject(); + obj.put("key", 33); + obj.put("not", "nan"); + + assertEquals(33, obj.getInt("key")); + assertNotFound(() -> obj.getInt("boo")); + assertJSONEx(() -> obj.getInt("not"), "JSONObject[\"not\"] is not a number."); + isTypeAndValue(33, Integer.class, obj.get("key")); + + assertEquals(33, obj.optInt("key")); + assertEquals(66, obj.optInt("boo", 66)); + assertEquals(0, obj.optInt("boo")); + } + + @Test + void numbers() { + Number tt = 33; + JSONObject obj = new JSONObject(); + obj.put("key", tt); + obj.put("not", "nan"); + + assertEquals(tt, obj.getNumber("key")); + assertNotFound(() -> obj.getNumber("boo")); + assertJSONEx(() -> obj.getNumber("not"), "JSONObject[\"not\"] is not a number."); + isTypeAndValue(tt, Number.class, obj.getNumber("key")); + + assertEquals(tt, obj.optNumber("key")); + assertEquals(66, obj.optNumber("boo", 66)); + assertEquals(0, obj.optNumber("boo")); + } + + @Test + void bigInts() { + JSONObject obj = new JSONObject(); + obj.put("key", BigInteger.valueOf(33)); + obj.put("not", "nan"); + + assertEquals(BigInteger.valueOf(33), obj.getBigInteger("key")); + assertNotFound(() -> obj.getBigInteger("boo")); + assertJSONEx(() -> obj.getBigInteger("not"), "JSONObject[\"not\"] is not a number."); + assertEquals(BigInteger.valueOf(33), obj.optBigInteger("key", BigInteger.TEN)); + assertEquals(BigInteger.TEN, obj.optBigInteger("boo", BigInteger.TEN)); + isTypeAndValue(33, Integer.class, obj.get("key")); + } + + @Test + void bigDecimal() { + BigDecimal value = BigDecimal.valueOf(33.5); + JSONObject obj = new JSONObject(); + obj.put("key", value); + obj.put("not", "nan"); + + assertEquals(value, obj.getBigDecimal("key")); + assertNotFound(() -> obj.getBigDecimal("boo")); + assertJSONEx(() -> obj.getBigDecimal("not"), "JSONObject[\"not\"] is not a number."); + assertEquals(value, obj.optBigDecimal("key", BigDecimal.TEN)); + assertEquals(BigDecimal.TEN, obj.optBigDecimal("boo", BigDecimal.TEN)); + isTypeAndValue(33.5, Double.class, obj.get("key")); + } + + @Test + void strings() { + JSONObject obj = new JSONObject(); + obj.put("key", "cheese"); + obj.put("not", 45); + + assertEquals("cheese", obj.getString("key")); + assertNotFound(() -> obj.getString("boo")); + assertEquals("45", obj.getString("not")); + assertEquals("cheese", obj.optString("key")); + assertEquals("logs", obj.optString("boo", "logs")); + assertEquals("", obj.optString("boo")); + isTypeAndValue("cheese", String.class, obj.get("key")); + } + + @Test + void jsonObjects() throws Exception { + JSONObject subObj = new JSONObject("{\"derp\": 42}"); + JSONObject obj = new JSONObject(); + obj.put("key", subObj); + obj.put("not", 45); + + assertEqualJson(subObj, obj.getJSONObject("key")); + assertNotFound(() -> obj.getJSONObject("boo")); + assertJSONEx(() -> obj.getJSONObject("not"), "JSONObject[\"not\"] is not a JSONObject."); + assertEqualJson(subObj, obj.optJSONObject("key")); + assertNull(obj.optJSONObject("boo")); + assertTrue(subObj.similar(obj.get("key"))); + } + + @Test + void jsonArrays() throws Exception { + var subObj = new JSONArray("[42]"); + JSONObject obj = new JSONObject(); + obj.put("key", subObj); + obj.put("not", 45); + + assertEqualJson(subObj, obj.getJSONArray("key")); + assertNotFound(() -> obj.getJSONArray("boo")); + assertJSONEx(() -> obj.getJSONArray("not"), "JSONObject[\"not\"] is not a JSONArray."); + assertEqualJson(subObj, obj.optJSONArray("key")); + assertNull(obj.optJSONArray("boo")); + assertTrue(subObj.similar(obj.get("key"))); + } + + @Test + void enums() { + JSONObject obj = new JSONObject(); + obj.put("key", fruit.orange); + obj.put("not", "nan"); + + assertEquals(fruit.orange, obj.getEnum(fruit.class, "key")); + assertJSONEx(() -> obj.getEnum(fruit.class, "not"), "JSONObject[\"not\"] is not an enum of type \"fruit\"."); + assertEquals(fruit.orange, obj.optEnum(fruit.class, "key")); + assertEquals(fruit.apple, obj.optEnum(fruit.class, "boo", fruit.apple)); + assertNull(obj.optEnum(fruit.class, "boo")); + isTypeAndValue(obj.get("key"), String.class, "orange"); + } + + @Test + void toStringIt() { + String str = "{\"foo\": 42}"; + + JSONObject obj = new JSONObject(str); + + assertEquals("{\"foo\":42}", obj.toString()); + } + + @Test + void toStringItIndent() { + String str = "{\"foo\": 42, \"bar\": true}"; + + JSONObject obj = new JSONObject(str); + + assertEquals("{\n" + + " \"foo\": 42,\n" + + " \"bar\": true\n" + + "}", obj.toString(3)); + } + + @Test + void objProperties() { + String str = "{\"foos\": [6,7,8]}"; + + JSONObject obj = new JSONObject(str); + + assertEquals(7, obj.getJSONArray("foos").get(1)); + assertEquals(7, obj.optJSONArray("foos").get(1)); + assertNull(obj.optJSONArray("bars")); + } + + @Test + void writer() { + String str = "{\"foo\":42}"; + + JSONObject obj = new JSONObject(str); + + StringWriter sw = new StringWriter(); + + obj.write(sw); + + assertEquals(str, sw.toString()); + } + + @Test + void writerIndent() { + String str = "{\"foo\": 42, \"bar\": true}"; + + JSONObject obj = new JSONObject(str); + + StringWriter sw = new StringWriter(); + + obj.write(sw, 3, 3); + + assertEquals("{\n" + + " \"foo\": 42,\n" + + " \"bar\": true\n" + + "}", sw.toString()); + } + + @Test + void remove() { + JSONObject obj = new JSONObject("{\"foo\": 42, \"bar\": true}"); + assertEquals(42, obj.remove("foo")); + assertNull(obj.remove("nothing")); + assertEquals("{\"bar\":true}", obj.toString()); + } + + @Test + void removeAThingThatDoesntExist() { + JSONObject obj = new JSONObject(); + obj.remove("foo"); + + assertEquals(0, obj.length()); + } + + @Test + void putReplace() { + JSONObject obj = new JSONObject("{\"bar\": 42}"); + assertEquals(42, obj.get("bar")); + assertSame(obj, obj.put("bar", 33)); + assertEquals(33, obj.get("bar")); + NullPointerException ex = assertThrows(NullPointerException.class, () -> obj.put(null, "hi")); + assertEquals("key == null", ex.getMessage()); + } + + @Test + void accumulateDoesNotCreate() { + JSONObject obj = new JSONObject(); + assertSame(obj, obj.accumulate("bar", 42)); + assertEquals(0, obj.length()); + } + + @Test + void accumulate() { + JSONObject obj = new JSONObject("{\"bar\": 42}"); + assertSame(obj, obj.accumulate("bar", 33)); + assertEquals(2, obj.getJSONArray("bar").length()); + assertEquals(42, obj.getJSONArray("bar").get(0)); + assertEquals(33, obj.getJSONArray("bar").get(1)); + } + + @Test + void accumulateNullKey() { + NullPointerException ex = assertThrows(NullPointerException.class, () -> new JSONObject().accumulate(null, "hi")); + assertEquals("Null key.", ex.getMessage()); + } + + @Test + void append() { + JSONObject obj = new JSONObject(); + assertSame(obj, obj.append("bar", 42)); + obj.append("bar", 33); + assertEquals(2, obj.getJSONArray("bar").length()); + assertEquals(42, obj.getJSONArray("bar").get(0)); + assertEquals(33, obj.getJSONArray("bar").get(1)); + } + + @Test + void appendNullKey() { + NullPointerException ex = assertThrows(NullPointerException.class, () -> new JSONObject().append(null, "hi")); + assertEquals("Null key.", ex.getMessage()); + } + + @Test + void appendToNotAnArrary() { + JSONObject obj = new JSONObject(); + assertSame(obj, obj.put("bar", "not")); + JSONException ex = assertThrows(JSONException.class, () -> obj.append("bar", 33)); + assertEquals("JSONObject[\"bar\"] is not a JSONArray.", ex.getMessage()); + } + + @Test + void increment() { + JSONObject obj = new JSONObject(); + assertSame(obj, obj.increment("cool-beans")); + assertEquals(1, obj.get("cool-beans")); + obj.increment("cool-beans") + .increment("cool-beans") + .increment("cool-beans"); + assertEquals(4, obj.get("cool-beans")); + } + + @Test + void incrementDouble() { + JSONObject obj = new JSONObject(); + assertSame(obj, obj.put("cool-beans", 1.5)); + obj.increment("cool-beans"); + assertEquals(2.5, obj.get("cool-beans")); + } + + + @Test + void putOnce() { + JSONObject obj = new JSONObject(); + assertSame(obj, obj.putOnce("foo", "bar")); + assertJSONEx(() -> obj.putOnce("foo", "baz"), "Duplicate key \"foo\""); + assertEquals("bar", obj.getString("foo")); + } + + @Test + void optPut() { + JSONObject obj = new JSONObject(); + assertSame(obj, obj.putOpt("foo", "bar")); + obj.putOpt(null, "bar"); + obj.putOpt("foo", null); + assertEquals("bar", obj.get("foo")); + obj.putOpt("foo", "qux"); + assertEquals("qux", obj.get("foo")); + } + + @Test + void keySet() { + JSONObject obj = new JSONObject(); + obj.put("one", "a"); + obj.put("two", "b"); + Set exp = newHashSet("one", "two"); + assertEquals(exp, obj.keySet()); + assertEquals(exp, newHashSet(obj.keys())); + } + + @Test + void similar() { + JSONObject obj1 = new JSONObject("{\"foo\":42}"); + JSONObject obj2 = new JSONObject("{\"foo\":42}"); + assertTrue(obj1.similar(obj2)); + obj1.put("foo", -9); + assertFalse(obj1.similar(obj2)); + } + + @Test + void query() { + JSONObject obj = new JSONObject("{\"a\":{\"b\": 42}}"); + assertEquals(42, obj.query("/a/b")); + } + + @Test + void maps() { + JSONObject obj = new JSONObject("{\"foo\": {\"bar\": 42}, \"qux\": 21474836475, \"baz\": 55}"); + + Map map = obj.toMap(); + assertEquals(55, map.get("baz")); + assertEquals(21474836475L, map.get("qux")); + JSONObject sub = (JSONObject) obj.get("foo"); + assertEquals(42, sub.get("bar")); + } + + @Test + void names() { + JSONObject obj = new JSONObject(of("foo", 1, "bar", 2, "baz", 3)); + var names = obj.names(); + assertEquals( + newHashSet("foo", "bar", "baz"), + newHashSet(names.toList()) + ); + } + + @Test + void toJSONArray() { + var o = new JSONObject(of("foo","bar","baz",42)); + + assertNull(o.toJSONArray(new JSONArray())); + + assertEquals(new JSONArray(asList("bar", 42)), + o.toJSONArray(new JSONArray(asList("foo", "baz")))); + + assertEquals(new JSONArray(asList(null, null)), + new JSONObject().toJSONArray(new JSONArray(asList("foo", "baz")))); + } + + @Test + void putCollection() { + var o = new JSONObject(); + o.put("foo", asList(1,2,3)); + assertEquals("{\"foo\":[1,2,3]}", o.toString()); + } + + @Test + void putObjectAsMap() { + var o = new JSONObject(); + o.put("foo", of("baz", 42)); + assertEquals("{\"foo\":{\"baz\":42}}", o.toString()); + } + + @Test + @SuppressWarnings("ConstantConditions") + void stringToValue() { + assertSame(JSONObject.NULL, JSONObject.stringToValue("null")); + assertEquals(true, JSONObject.stringToValue("true")); + assertEquals(false, JSONObject.stringToValue("false")); + assertEquals(42, JSONObject.stringToValue("42")); + assertEquals(45.25, JSONObject.stringToValue("45.25")); + assertEquals(-45.25, JSONObject.stringToValue("-45.25")); + NullPointerException ex = assertThrows(NullPointerException.class, () -> JSONObject.stringToValue(null)); + } + + @Test + void quote() { + assertEquals("\"\\\"foo\\\"hoo\"", JSONObject.quote("\"foo\"hoo")); + } + + @Test + void quoteWriter() throws IOException { + StringWriter w = new StringWriter(); + Writer quote = JSONObject.quote("\"foo\"hoo", w); + assertEquals("\"\\\"foo\\\"hoo\"", quote.toString()); + } + + @Test + void wrapPrimitives() { + assertEquals(42, JSONObject.wrap(42)); + assertEquals(42.5, JSONObject.wrap(42.5)); + assertSame(JSONObject.NULL, JSONObject.wrap(null)); + assertEquals(true, JSONObject.wrap(true)); + } + + @Test + void wrapObjects() { + assertTrue(new JSONArray(asList(1,2,3)).similar(JSONObject.wrap(asList(1,2,3)))); + assertTrue(new JSONArray(asList(1,2,3)).similar(JSONObject.wrap(new int[]{1,2,3}))); + assertTrue(new JSONObject(of("f",1)).similar(JSONObject.wrap(of("f",1)))); + assertTrue(new JSONObject().similar(JSONObject.wrap(new Foo("hi")))); + } + + @Test + void doubleToString() { + assertEquals("42", JSONObject.doubleToString(42)); + assertEquals("42.5643", JSONObject.doubleToString(42.5643)); + } + + @Test + void numberToString() { + assertEquals("42", JSONObject.numberToString(42)); + assertEquals("42.5643", JSONObject.numberToString(42.5643f)); + } + + @Test + void valueToString() { + assertEquals("null", JSONObject.valueToString(null)); + assertEquals("42", JSONObject.valueToString(42)); + assertEquals("42.5643", JSONObject.valueToString(42.5643f)); + assertEquals("\"Hello World\"", JSONObject.valueToString("Hello World")); + assertEquals("{\"bar\":\"me\"}", JSONObject.valueToString(new Foo("me"))); + assertEquals("{}", JSONObject.valueToString(new JSONObject())); + assertEquals("[]", JSONObject.valueToString(new JSONArray())); + } + + @Test + void getNames() { + assertArrayEquals(null, JSONObject.getNames(new JSONObject())); + assertArrayEquals(new String[]{"a","b"}, JSONObject.getNames(new JSONObject(of("a",1,"b",2)))); + } + + private void assertNotFound(Executable exRunnable) { + assertJSONEx(exRunnable, "JSONObject[\"boo\"] not found."); + } + + public static void assertJSONEx(Executable exRunnable, String message) { + JSONException ex = assertThrows(JSONException.class, exRunnable); + assertEquals(message, ex.getMessage()); + } + + public static void assertEqualJson(Object subObj, Object value){ + try { + JSONAssert.assertEquals(subObj.toString(), value.toString(), true); + } catch (org.json.JSONException e) { + throw new UnirestException(e); + } + } + + public static void isTypeAndValue(Object o, Class type, Object value) { + assertEquals(o, value); + assertTrue(type.isInstance(o)); + } + + public enum fruit {orange, apple} + + public static class TestMe { + private boolean aBoolean; + private String aSillyString; + private Integer aNumber; + private TestMe aSub; + + public TestMe(){ } + + public TestMe(boolean bool, String aString, int aNumber, TestMe aSub){ + this.aBoolean = bool; + this.aSillyString = aString; + this.aNumber = aNumber; + this.aSub = aSub; + } + + public boolean isaBoolean() { + return aBoolean; + } + + public void setaBoolean(boolean aBoolean) { + this.aBoolean = aBoolean; + } + + public String getaSillyString() { + return aSillyString; + } + + public void setaSillyString(String aSillyString) { + this.aSillyString = aSillyString; + } + + public Integer getaNumber() { + return aNumber; + } + + public void setaNumber(Integer aNumber) { + this.aNumber = aNumber; + } + + public TestMe getaSub() { + return aSub; + } + + public void setaSub(TestMe aSub) { + this.aSub = aSub; + } + } +} diff --git a/unirest-modules-gson/src/test/java/kong/unirest/modules/gson/JSONPointerTest.java b/unirest-modules-gson/src/test/java/kong/unirest/modules/gson/JSONPointerTest.java new file mode 100644 index 000000000..e8b03cac8 --- /dev/null +++ b/unirest-modules-gson/src/test/java/kong/unirest/modules/gson/JSONPointerTest.java @@ -0,0 +1,187 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.modules.gson; + +import com.google.common.base.Charsets; +import com.google.common.io.Resources; +import kong.unirest.core.json.JSONObject; +import kong.unirest.core.json.JSONPointer; +import kong.unirest.core.json.JSONPointerException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +import static org.junit.jupiter.api.Assertions.*; + +class JSONPointerTest { + // https://tools.ietf.org/html/rfc6901 + private static final String RFC_TEST = getResource("JSON_POINTER_REF.json"); + private final JSONObject obj = new JSONObject(RFC_TEST); + + public static String getResource(String resourceName){ + try { + return Resources.toString(Resources.getResource(resourceName), Charsets.UTF_8); + }catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Test + void nullQuery() { + var ex = assertThrows(NullPointerException.class, () -> obj.query((String)null)); + assertEquals("pointer cannot be null", ex.getMessage()); + } + + @Test + void invalidPathQuery() { + var ex = assertThrows(IllegalArgumentException.class, () -> obj.query("foo")); + assertEquals("a JSON pointer should start with '/' or '#/'", ex.getMessage()); + } + + @Test + void invalidPathQuery_downpath() { + var ex = assertThrows(JSONPointerException.class, () -> obj.query("/shwoop/dedoop")); + assertEquals("Path Segment Missing: shwoop", ex.getMessage()); + } + + @Test + void arrayPartThatDoesNotExist() { + var ex = assertThrows(JSONPointerException.class, () -> obj.query("/foo/5")); + assertEquals("index 5 is out of bounds - the array has 2 elements", ex.getMessage()); + } + + @Test + void referenceAnArrayAsAThing() { + var ex = assertThrows(JSONPointerException.class, () -> obj.query("/foo/bar")); + assertEquals("bar is not an array index", ex.getMessage()); + } + + @Test + @SuppressWarnings("RedundantCast") + void constructorMayNotTakeNull() { + var ex = assertThrows(NullPointerException.class, () -> new JSONPointer((String) null)); + assertEquals("pointer cannot be null", ex.getMessage()); + } + + @Test + void toStringReturnsOriginalString() { + assertEquals("/foo/g~0h/baz", new JSONPointer("/foo/g~h/baz").toString()); + assertEquals("/foo/g~0h/baz", JSONPointer.compile("/foo/g~h/baz").toString()); + } + + @Test + void canGetAsURIFragmanet() { + assertEquals("#/foo/g%7Eh/baz", new JSONPointer("/foo/g~h/baz").toURIFragment()); + } + + @Test + void elementInObjectDoesNotExist() { + assertNull(obj.query("/derpa")); + } + + @Test + void testRef_all() throws Exception { + assertQueryJson(RFC_TEST, ""); + } + + @Test + void testRef_Array() throws Exception { + assertQueryJson("[\"bar\", \"baz\"]", "/foo"); + } + + @Test + void testRef_ArrayZero() { + assertEquals("bar", obj.query("/foo/0").toString()); + } + + @Test + void testRef_Slash() { + assertEquals(0, obj.query("/")); + } + + @Test + void testRef_ab() { + assertEquals(1, obj.query("/a~1b")); + } + + @Test + void testRef_cd() { + assertEquals(2, obj.query("/c%d")); + } + + @Test + void testRef_ef() { + assertEquals(3, obj.query("/e^f")); + } + + @Test + void testRef_gh() { + assertEquals(4, obj.query("/g|h")); + } + + @Test + void testRef_ij() { + assertEquals(5, obj.query("/i\\j")); + } + + @Test + void testRef_kl() { + assertEquals(6, obj.query("/k\"l")); + } + + @Test + void testRef_space() { + assertEquals(7, obj.query("/ ")); + } + + @Test + void testRef_mn() { + assertEquals(8, obj.query("/m~0n")); + } + + @Test + void letsGoDeep() { + assertEquals(true, obj.query("/cucu/0/banana/pants")); + } + + @Test + void builder(){ + var pointer = JSONPointer + .builder() + .append("foo") + .append(4) + .append("n~t") + .append("bar/1") + .build(); + + assertEquals(new JSONPointer("/foo/4/n~0t/bar/1").toString(), + pointer.toString()); + } + + private void assertQueryJson(String s, String s2) throws Exception { + JSONAssert.assertEquals(s, obj.query(s2).toString(), true); + } + +} diff --git a/unirest-modules-gson/src/test/java/kong/unirest/modules/gson/JsonObjectMapperTest.java b/unirest-modules-gson/src/test/java/kong/unirest/modules/gson/JsonObjectMapperTest.java new file mode 100644 index 000000000..1ee303282 --- /dev/null +++ b/unirest-modules-gson/src/test/java/kong/unirest/modules/gson/JsonObjectMapperTest.java @@ -0,0 +1,348 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.modules.gson; + +import kong.unirest.core.UnirestException; +import kong.unirest.core.json.JSONObject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZonedDateTime; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +import static org.junit.jupiter.api.Assertions.*; + +class JsonObjectMapperTest { + + GsonObjectMapper om = new GsonObjectMapper(); + + @BeforeEach + void before() { + TimeZone.setDefault(TimeZone.getTimeZone("UTC")); + + } + + @Test + void serializeEverythingNull() { + var test = new TestDates(); + String actual = om.writeValue(test); + assertEquals("{}", actual); + } + + @Test + void serializeDate_iso_with_time() { + var date = getDate("1985-07-03T18:00:00.042Z"); + + String actual = om.writeValue(date); + + assertEquals("{\"date\":\"1985-07-03T18:00:00.042Z\"}", actual); + } + + @Test + void serializeDate_iso_no_time() { + var date = getDate(new Date(489196800000L)); + + String actual = om.writeValue(date); + + assertEquals("{\"date\":\"1985-07-03T00:00:00Z\"}", actual); + } + + @Test + void deserializeDate_null() { + var back = getTestDate("date", null); + + assertNull(back.getDate()); + } + + @Test + void deserializeDate_iso_with_time() { + var back = getTestDate("date", "1985-07-03T18:30:00.042Z"); + + assertEquals(489263400042L, back.getDate().getTime()); + } + + @Test + void deserializeDate_iso_with_NoTime() { + var back = getTestDate("date", "1985-07-03"); + + assertEquals(489196800000L, back.getDate().getTime()); + } + + @Test + void deserializeDate_iso_datetime_noMillies() { + var back = getTestDate("date", "1985-07-03T18:30:00Z"); + + assertEquals(489263400000L, back.getDate().getTime()); + } + + @Test + void deserializeDate_iso_datetime_Errors() { + var ex = assertThrows(UnirestException.class, () -> getTestDate("date", "Leeeeeeeroy Jenkins!")); + assertEquals("Could Not Parse as java.util.Date: Leeeeeeeroy Jenkins!", ex.getMessage()); + } + + @Test + void deserializeDate_iso_datetime_noSeconds() { + var back = getTestDate("date", "1985-07-03T18:30Z"); + + assertEquals(489263400000L, back.getDate().getTime()); + } + + @Test + void getDatesFromNumbers() { + var back = getTestDate("date", 42); + + assertEquals(new Date(42), back.getDate()); + } + + @Test + void canSerializeCalendar() { + var test = getCalendar("1985-07-03T18:00:00.042Z"); + + String actual = om.writeValue(test); + assertEquals("{\"calendar\":\"1985-07-03T18:00:00.042Z\"}", actual); + } + + @Test + @SuppressWarnings("MagicConstant") + void canSerializeCalendar_no_time() { + var cal = GregorianCalendar.getInstance(); + cal.set(1985, 6, 3, 0, 0, 0); + cal.set(Calendar.MILLISECOND, 0); + var test = getCalendar(cal); + + String actual = om.writeValue(test); + assertEquals("{\"calendar\":\"1985-07-03T00:00:00Z\"}", actual); + } + + @Test + void deserializeCalendar_iso_datetime() { + var back = getTestDate("calendar", "1985-07-03T18:30:00.042Z"); + + assertEquals(489263400042L, back.getCalendar().getTimeInMillis()); + } + + @Test + void deserializeCalendar_iso_datetime_noMillies() { + var back = getTestDate("calendar", "1985-07-03T18:30:00Z"); + + assertEquals(489263400000L, back.getCalendar().getTimeInMillis()); + } + + @Test + void deserializeCalendar_iso_datetime_noSeconds() { + var back = getTestDate("calendar", "1985-07-03T18:30Z"); + + assertEquals(489263400000L, back.getCalendar().getTimeInMillis()); + } + + @Test + void deserializeCalendar_iso_date() { + var back = getTestDate("calendar", "1985-07-03"); + + assertEquals(489196800000L, back.getCalendar().getTimeInMillis()); + } + + @Test + void deserializeCalendar_iso_datetime_Errors() { + var ex = assertThrows(UnirestException.class, () -> getTestDate("calendar", "Leeeeeeeroy Jenkins!")); + assertEquals("Could Not Parse as java.util.Calendar: Leeeeeeeroy Jenkins!", ex.getMessage()); + } + + @Test + void canSerializeZonedDateTimes() { + var zonedDt = ZonedDateTime.parse("1985-07-03T18:00:00.042Z").withFixedOffsetZone(); + var test = new TestDates(); + test.setZonedDateTime(zonedDt); + + String actual = om.writeValue(test); + assertEquals("{\"zonedDateTime\":\"1985-07-03T18:00:00.042Z\"}", actual); + } + + @Test + void deserializeZonedDateTime_iso_datetime() { + var back = getTestDate("zonedDateTime", "1985-07-03T18:30:00.042Z"); + + assertEquals(ZonedDateTime.parse("1985-07-03T18:30:00.042Z"), back.getZonedDateTime()); + } + + @Test + void deserializeZonedDateTime_iso_with_offset() { + var back = getTestDate("zonedDateTime", "1985-07-03T18:30:00.042+02:00"); + + assertEquals(ZonedDateTime.parse("1985-07-03T18:30:00.042+02:00"), back.getZonedDateTime()); + } + + @Test + void canSerializeLocalDateTimes() { + var zonedDt = LocalDateTime.parse("1985-07-03T18:00:00.042"); + var test = new TestDates(); + test.setLocalDateTime(zonedDt); + + String actual = om.writeValue(test); + assertEquals("{\"localDateTime\":\"1985-07-03T18:00:00.042\"}", actual); + } + + @Test + void deserializeLocalDateTime_iso_datetime() { + var back = getTestDate("localDateTime", "1985-07-03T18:00:00.042"); + + assertEquals(LocalDateTime.parse("1985-07-03T18:00:00.042"), back.getLocalDateTime()); + } + + @Test + void deserializeLocalDateTime_iso_datetime_noTime() { + var back = getTestDate("localDateTime", "1985-07-03"); + + assertEquals(LocalDateTime.parse("1985-07-03T00:00"), back.getLocalDateTime()); + } + + @Test + void canSerializeLocalDate() { + var zonedDt = LocalDate.parse("1985-07-03"); + var test = new TestDates(); + test.setLocalDate(zonedDt); + + String actual = om.writeValue(test); + assertEquals("{\"localDate\":\"1985-07-03\"}", actual); + } + + @Test + void deserializeLocalDate_iso_datetime() { + var back = getTestDate("localDate", "1985-07-03T18:00:00.042"); + + assertEquals(LocalDate.parse("1985-07-03"), back.getLocalDate()); + } + + @Test + void deserializeLocalDate_iso_datetime_noTime() { + var back = getTestDate("localDate", "1985-07-03"); + + assertEquals(LocalDate.parse("1985-07-03"), back.getLocalDate()); + } + + @Test + void doNotEscapeHTML() { + var s = new TestString(); + s.test = "it's a && b || c + 1!?"; + + String res = om.writeValue(s); + + assertEquals("{\"test\":\"it's a && b || c + 1!?\"}", res); + } + + private static class TestString { + private String test; + } + + private TestDates getTestDate(String key, Object date) { + return om.readValue(getJson(key, date), TestDates.class); + } + + private String getJson(String key, Object value){ + JSONObject object = new JSONObject(); + object.put(key, value); + return object.toString(); + } + + @SuppressWarnings("SameParameterValue") + private TestDates getDate(String date) { + Date from = Date.from(ZonedDateTime.parse(date).withFixedOffsetZone().toInstant()); + return getDate(from); + } + + private TestDates getDate(Date from) { + var test = new TestDates(); + test.setDate(from); + return test; + } + + @SuppressWarnings("SameParameterValue") + private TestDates getCalendar(String date) { + Calendar from = GregorianCalendar.from(ZonedDateTime.parse(date)); + return getCalendar(from); + } + + private TestDates getCalendar(Calendar from) { + var test = new TestDates(); + test.setCalendar(from); + return test; + } + + public static class TestDates { + + private Date date; + private Calendar calendar; + private ZonedDateTime zonedDateTime; + private LocalDateTime localDateTime; + private LocalDate localDate; + + public Date getDate() { + return date; + } + + public void setDate(Date date) { + this.date = date; + } + + public Calendar getCalendar() { + return calendar; + } + + public void setCalendar(Calendar calendar) { + this.calendar = calendar; + } + + public ZonedDateTime getZonedDateTime() { + return zonedDateTime; + } + + public void setZonedDateTime(ZonedDateTime zonedDateTime) { + this.zonedDateTime = zonedDateTime; + } + + public LocalDateTime getLocalDateTime() { + return localDateTime; + } + + public void setLocalDateTime(LocalDateTime localDateTime) { + this.localDateTime = localDateTime; + } + + public LocalDate getLocalDate() { + return localDate; + } + + public void setLocalDate(LocalDate localDate) { + this.localDate = localDate; + } + } +} \ No newline at end of file diff --git a/unirest-modules-gson/src/test/resources/JSON_POINTER_REF.json b/unirest-modules-gson/src/test/resources/JSON_POINTER_REF.json new file mode 100644 index 000000000..9fc119831 --- /dev/null +++ b/unirest-modules-gson/src/test/resources/JSON_POINTER_REF.json @@ -0,0 +1,19 @@ +{ + "foo": ["bar", "baz"], + "": 0, + "a/b": 1, + "c%d": 2, + "e^f": 3, + "g|h": 4, + "i\\j": 5, + "k\"l": 6, + " ": 7, + "m~n": 8, + "cucu": [ + { + "banana": { + "pants" : true + } + } + ] +} \ No newline at end of file diff --git a/unirest-modules-jackson-legacy/pom.xml b/unirest-modules-jackson-legacy/pom.xml new file mode 100644 index 000000000..b7899c66b --- /dev/null +++ b/unirest-modules-jackson-legacy/pom.xml @@ -0,0 +1,37 @@ + + + 4.0.0 + + com.konghq + unirest-java-parent + 4.10.3-SNAPSHOT + + + unirest-modules-jackson-legacy + unirest-modules-jackson-legacy + Jackson 2 based object mapper for Unirest + + + ${project.parent.basedir} + 2.22.3 + + + + + com.konghq + unirest-java-core + ${project.version} + provided + + + com.fasterxml.jackson.core + jackson-databind + ${legacy-jackson.version} + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${legacy-jackson.version} + + + \ No newline at end of file diff --git a/unirest-modules-jackson-legacy/src/main/java/kong/unirest/modules/jackson2/JacksonArray.java b/unirest-modules-jackson-legacy/src/main/java/kong/unirest/modules/jackson2/JacksonArray.java new file mode 100644 index 000000000..0496e80b8 --- /dev/null +++ b/unirest-modules-jackson-legacy/src/main/java/kong/unirest/modules/jackson2/JacksonArray.java @@ -0,0 +1,145 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.modules.jackson2; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.NullNode; +import kong.unirest.core.json.JsonEngine; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +class JacksonArray extends JacksonElement implements JsonEngine.Array { + JacksonArray(ArrayNode element) { + super(element); + } + + @Override + public int size() { + return element.size(); + } + + @Override + public JsonEngine.Element get(int index) { + validateIndex(index); + return wrap(element.get(index)); + } + + private void validateIndex(int index) { + if(element.size() < index +1){ + throw new IndexOutOfBoundsException(); + } + } + + + @Override + public JsonEngine.Element remove(int index) { + return wrap(element.remove(index)); + } + + @Override + public JsonEngine.Element put(int index, Number number) { + if(number instanceof Integer){ + element.insert(index, (Integer) number); + } else if (number instanceof Double){ + element.insert(index, (Double)number); + } else if (number instanceof BigInteger) { + element.insert(index, (BigInteger) number); + } else if (number instanceof Float){ + element.insert(index, (Float)number); + } else if(number instanceof BigDecimal) { + element.insert(index, (BigDecimal) number); + } + return this; + } + + @Override + public JsonEngine.Element put(int index, String value) { + element.insert(index, value); + return this; + } + + @Override + public JsonEngine.Element put(int index, Boolean value) { + element.insert(index, value); + return this; + } + + @Override + public void add(JsonEngine.Element obj) { + if(obj == null){ + element.add(NullNode.getInstance()); + return; + } + element.add((JsonNode) obj.getEngineElement()); + } + + @Override + public void set(int index, JsonEngine.Element o) { + if(o == null){ + element.set(index, NullNode.getInstance()); + } else { + element.set(index, (JsonNode)o.getEngineElement()); + } + } + + @Override + public void add(Number number) { + if(number instanceof Integer){ + element.add((Integer) number); + } else if (number instanceof Double){ + element.add((Double)number); + } else if (number instanceof Long){ + element.add((Long)number); + } else if (number instanceof BigInteger) { + element.add((BigInteger) number); + } else if (number instanceof Float){ + element.add((Float)number); + } else if(number instanceof BigDecimal) { + element.add((BigDecimal) number); + } + } + + @Override + public void add(String str) { + element.add(str); + } + + @Override + public void add(Boolean bool) { + element.add(bool); + } + + @Override + public String join(String token) { + return StreamSupport.stream(element.spliterator(), false) + .map(String::valueOf) + .collect(Collectors.joining(token)); + } +} diff --git a/unirest-modules-jackson-legacy/src/main/java/kong/unirest/modules/jackson2/JacksonElement.java b/unirest-modules-jackson-legacy/src/main/java/kong/unirest/modules/jackson2/JacksonElement.java new file mode 100644 index 000000000..1abdc2972 --- /dev/null +++ b/unirest-modules-jackson-legacy/src/main/java/kong/unirest/modules/jackson2/JacksonElement.java @@ -0,0 +1,184 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.modules.jackson2; + + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.NullNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.ValueNode; +import kong.unirest.core.json.*; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Objects; + +class JacksonElement implements JsonEngine.Element { + protected T element; + + JacksonElement(T element){ + this.element = element; + } + + static JsonEngine.Element wrap(JsonNode node) { + if(node == null || node.isNull()){ + return new JacksonPrimitive(NullNode.getInstance()); + } else if(node.isArray()){ + return new JacksonArray((ArrayNode) node); + } else if(node.isObject()){ + return new JacksonObject((ObjectNode)node); + } else if (node.isValueNode()){ + return new JacksonPrimitive((ValueNode)node); + } + return new JacksonPrimitive(NullNode.getInstance()); + } + + @Override + public JsonEngine.Object getAsJsonObject() { + if(element.isObject()) { + return new JacksonObject((ObjectNode) element); + } + throw new IllegalStateException("Not an object"); + } + + @Override + public boolean isJsonNull() { + return element instanceof NullNode; + } + + @Override + public JsonEngine.Primitive getAsJsonPrimitive() { + return new JacksonPrimitive((ValueNode) element); + } + + @Override + public JsonEngine.Array getAsJsonArray() { + if(!element.isArray()){ + throw new IllegalStateException("Not an Array"); + } + return new JacksonArray((ArrayNode)element); + } + + @Override + public float getAsFloat() { + if(!element.isFloat()){ + throw new NumberFormatException("not a float"); + } + return element.floatValue(); + } + + @Override + public double getAsDouble() { + if(!element.isNumber()){ + throw new NumberFormatException("not a double"); + } + return element.asDouble(); + } + + @Override + public String getAsString() { + return element.asText(); + } + + @Override + public long getAsLong() { + if(!element.isLong() && !element.isIntegralNumber()){ + throw new NumberFormatException("not a long"); + } + return element.asLong(); + } + + @Override + public int getAsInt() { + if(!element.isIntegralNumber()) { + throw new NumberFormatException("Not a number"); + } + return element.asInt(); + } + + @Override + public boolean getAsBoolean() { + return element.asBoolean(); + } + + @Override + public BigInteger getAsBigInteger() { + if(!element.isIntegralNumber()) { + throw new NumberFormatException("Not an integer"); + } + return element.bigIntegerValue(); + } + + @Override + public BigDecimal getAsBigDecimal() { + if(!element.isNumber()){ + throw new NumberFormatException("Not a decimal"); + } + return element.decimalValue(); + } + + @Override + public JsonEngine.Primitive getAsPrimitive() { + if(element.isValueNode()){ + return new JacksonPrimitive((ValueNode) element); + } + throw new JSONException("Not a value type"); + } + + @Override + public boolean isJsonArray() { + return element.isArray(); + } + + @Override + public boolean isJsonPrimitive() { + return element.isValueNode(); + } + + @Override + public boolean isJsonObject() { + return element.isObject(); + } + + @Override + public T getEngineElement() { + return (T)element; + } + + @Override + public boolean equals(Object o) { + if (this == o) {return true;} + if (o == null || getClass() != o.getClass()) {return false;} + JacksonElement that = (JacksonElement) o; + return Objects.equals(element, that.element); + } + + @Override + public int hashCode() { + return Objects.hash(element); + } +} diff --git a/unirest-modules-jackson-legacy/src/main/java/kong/unirest/modules/jackson2/JacksonEngine.java b/unirest-modules-jackson-legacy/src/main/java/kong/unirest/modules/jackson2/JacksonEngine.java new file mode 100644 index 000000000..59ebabb3e --- /dev/null +++ b/unirest-modules-jackson-legacy/src/main/java/kong/unirest/modules/jackson2/JacksonEngine.java @@ -0,0 +1,218 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.modules.jackson2; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.json.JsonReadFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.databind.node.*; +import kong.unirest.core.ObjectMapper; +import kong.unirest.core.UnirestException; +import kong.unirest.core.json.*; + +import java.io.IOException; +import java.io.Writer; +import java.math.BigInteger; +import java.util.Collection; + +public class JacksonEngine implements JsonEngine { + private com.fasterxml.jackson.databind.ObjectMapper om; + private ObjectMapper objm; + + public JacksonEngine(){ + objm = new JacksonObjectMapper(); + om = JsonMapper.builder() + .enable(JsonReadFeature.ALLOW_UNQUOTED_FIELD_NAMES) + .build(); + } + + @Override + public String toPrettyJson(Element obj) { + try { + return om.writerWithDefaultPrettyPrinter() + .writeValueAsString(obj.getEngineElement()); + } catch (JsonProcessingException e) { + throw new UnirestException(e); + } + } + + @Override + public String toJson(Element obj) { + try { + return om.writeValueAsString(obj.getEngineElement()); + } catch (JsonProcessingException e) { + throw new UnirestException(e); + } + } + + @Override + public void toJson(Element obj, Writer sw) { + try { + om.writeValue(sw, obj.getEngineElement()); + } catch (IOException e) { + throw new JSONException(e); + } + } + + @Override + public void toPrettyJson(Element obj, Writer sw) { + try { + om.writerWithDefaultPrettyPrinter() + .writeValue(sw, obj.getEngineElement()); + } catch (IOException e) { + throw new JSONException(e); + } + } + + @Override + public Element toJsonTree(java.lang.Object obj) { + return JacksonElement.wrap(om.convertValue(obj, JsonNode.class)); + } + + @Override + public Object newEngineObject() { + return new JacksonObject(om.createObjectNode()); + } + + @Override + public Object newEngineObject(String string) throws JSONException { + try { + return new JacksonObject(om.readValue(string, ObjectNode.class)); + } catch (JsonProcessingException e) { + throw new JSONException("Invalid JSON"); + } + } + + @Override + public Array newJsonArray(String jsonString) throws JSONException { + try { + return new JacksonArray(om.readValue(jsonString, ArrayNode.class)); + } catch (JsonProcessingException e) { + throw new JSONException("Invalid JSON"); + } + } + + @Override + public Array newJsonArray(Collection collection) { + JacksonArray a = new JacksonArray(om.createArrayNode()); + for(java.lang.Object o : collection){ + add(a, o); + } + return a; + } + + private void add(JacksonArray a, java.lang.Object o) { + if(o instanceof Number){ + a.add((Number) o); + } else if (o instanceof String) { + a.add((String) o); + }else if (o instanceof Boolean) { + a.add((Boolean) o); + }else if(o instanceof JSONElement) { + a.add(((JSONElement) o).getElement()); + } else if(o instanceof Element) { + a.add((Element) o); + } else { + JsonNode tree = om.convertValue(o, JsonNode.class); + a.add(JacksonElement.wrap(tree)); + } + } + + @Override + public Array newEngineArray() { + return new JacksonArray(om.createArrayNode()); + } + + @Override + public T fromJson(Element obj, Class mapClass) { + return om.convertValue(obj.getEngineElement(), mapClass); + } + + @Override + public Primitive newJsonPrimitive(T enumValue) { + if (enumValue == null){ + return new JacksonPrimitive(NullNode.getInstance()); + } + return newJsonPrimitive(enumValue.name()); + } + + @Override + public Primitive newJsonPrimitive(String string) { + return convert(string, v -> new TextNode(v)); + } + + @Override + public Primitive newJsonPrimitive(Number number) { + if(number instanceof Integer) { + return convert((Integer) number, IntNode::new); + }else if (number instanceof Long){ + return convert((Long)number, LongNode::new); + } else if (number instanceof Double){ + return convert((Double)number, DoubleNode::new); + } else if (number instanceof BigInteger) { + return convert((BigInteger)number, BigIntegerNode::new); + } else if (number instanceof Float){ + return convert((Float)number, FloatNode::new); + } + return new JacksonPrimitive(NullNode.getInstance()); + } + + @Override + public Primitive newJsonPrimitive(Boolean bool) { + return convert(bool, v -> BooleanNode.valueOf(v)); + } + + @Override + public ObjectMapper getObjectMapper() { + return objm; + } + + @Override + public String quote(java.lang.Object s) { + try { + return om.writeValueAsString(s); + } catch (JsonProcessingException e) { + throw new JSONException(e); + } + } + + @FunctionalInterface + private interface ValueSupplier { + ValueNode getIt(V value) throws JsonProcessingException; + } + + private Primitive convert(T value, ValueSupplier supplier){ + try { + if (value == null){ + return new JacksonPrimitive(NullNode.getInstance()); + } + return new JacksonPrimitive(supplier.getIt(value)); + } catch (JsonProcessingException e) { + throw new UnirestException(e); + } + } +} diff --git a/unirest-modules-jackson-legacy/src/main/java/kong/unirest/modules/jackson2/JacksonObject.java b/unirest-modules-jackson-legacy/src/main/java/kong/unirest/modules/jackson2/JacksonObject.java new file mode 100644 index 000000000..e673b5625 --- /dev/null +++ b/unirest-modules-jackson-legacy/src/main/java/kong/unirest/modules/jackson2/JacksonObject.java @@ -0,0 +1,122 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.modules.jackson2; + +import com.fasterxml.jackson.databind.node.NullNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import kong.unirest.core.json.JsonEngine; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +class JacksonObject extends JacksonElement implements JsonEngine.Object { + + public JacksonObject(ObjectNode element) { + super(element); + } + + @Override + public int size() { + return element.size(); + } + + @Override + public boolean has(String key) { + return element.has(key); + } + + @Override + public JsonEngine.Element get(String key) { + return wrap(element.get(key)); + } + + @Override + public void add(String key, JsonEngine.Element value) { + element.set(key, value.getEngineElement()); + } + + @Override + public void addProperty(String key, Boolean value) { + element.put(key, value); + } + + @Override + public void addProperty(String key, String value) { + element.put(key, value); + } + + @Override + public void addProperty(String key, Number number) { + if(number instanceof Integer){ + element.put(key, (Integer) number); + } else if (number instanceof Double){ + element.put(key, (Double)number); + } else if (number instanceof BigInteger) { + element.put(key, (BigInteger) number); + } else if (number instanceof Float){ + element.put(key, (Float)number); + } else if(number instanceof BigDecimal) { + element.put(key, (BigDecimal) number); + } else if (number instanceof Long) { + element.put(key, (Long)number); + } + } + + @Override + public void addProperty(String key, JsonEngine.Element value) { + element.set(key, value.getEngineElement()); + } + + @Override + public void remove(String key) { + element.remove(key); + } + + @Override + public void add(String key, E enumValue) { + if(enumValue == null){ + element.set(key, NullNode.getInstance()); + } else { + element.put(key, enumValue.name()); + } + } + + @Override + public Set keySet() { + return StreamSupport.stream( + Spliterators.spliteratorUnknownSize( + element.fieldNames(), + Spliterator.ORDERED) + , false) + .collect(Collectors.toSet()); + + } +} diff --git a/unirest-modules-jackson-legacy/src/main/java/kong/unirest/modules/jackson2/JacksonObjectMapper.java b/unirest-modules-jackson-legacy/src/main/java/kong/unirest/modules/jackson2/JacksonObjectMapper.java new file mode 100644 index 000000000..b97383168 --- /dev/null +++ b/unirest-modules-jackson-legacy/src/main/java/kong/unirest/modules/jackson2/JacksonObjectMapper.java @@ -0,0 +1,118 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* +The MIT License + +Copyright for portions of unirest-java are held by Kong Inc (c) 2018. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +package kong.unirest.modules.jackson2; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import kong.unirest.core.GenericType; +import kong.unirest.core.ObjectMapper; +import kong.unirest.core.UnirestException; + +import java.io.IOException; +import java.util.function.Consumer; + + +public class JacksonObjectMapper implements ObjectMapper { + private final com.fasterxml.jackson.databind.ObjectMapper om; + + public JacksonObjectMapper(){ + this(c -> {}); + } + + /** + * Pass in any additional ObjectMapper configurations you want + * @param configurations consumer of confiruations to perform on the com.fasterxml.jackson.databind.ObjectMapper + */ + public JacksonObjectMapper(Consumer configurations) { + this(new com.fasterxml.jackson.databind.ObjectMapper()); + om.configure(JsonGenerator.Feature.IGNORE_UNKNOWN, true); + om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + //om.configure(WRITE_DATES_AS_TIMESTAMPS, false); + om.registerModule(new JavaTimeModule()); + configurations.accept(om); + } + + public JacksonObjectMapper(com.fasterxml.jackson.databind.ObjectMapper om){ + this.om = om; + } + + public com.fasterxml.jackson.databind.ObjectMapper getJacksonMapper(){ + return om; + } + + @Override + public T readValue(String value, Class valueType) { + try { + return om.readValue(value, valueType); + } catch (IOException e) { + throw new UnirestException(e); + } + } + + @Override + public T readValue(String value, GenericType genericType) { + try { + return om.readValue(value, om.constructType(genericType.getType())); + } catch (IOException e) { + throw new UnirestException(e); + } + } + + @Override + public String writeValue(Object value) { + try { + return om.writeValueAsString(value); + } catch (JsonProcessingException e) { + throw new UnirestException(e); + } + } +} diff --git a/unirest-modules-jackson-legacy/src/main/java/kong/unirest/modules/jackson2/JacksonPrimitive.java b/unirest-modules-jackson-legacy/src/main/java/kong/unirest/modules/jackson2/JacksonPrimitive.java new file mode 100644 index 000000000..748577581 --- /dev/null +++ b/unirest-modules-jackson-legacy/src/main/java/kong/unirest/modules/jackson2/JacksonPrimitive.java @@ -0,0 +1,45 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.modules.jackson2; + +import com.fasterxml.jackson.databind.node.ValueNode; +import kong.unirest.core.json.JsonEngine; + +class JacksonPrimitive extends JacksonElement implements JsonEngine.Primitive { + public JacksonPrimitive(ValueNode element) { + super(element); + } + + @Override + public boolean isBoolean() { + return element.isBoolean(); + } + + @Override + public boolean isNumber() { + return element.isNumber(); + } +} diff --git a/unirest-modules-jackson-legacy/src/main/resources/META-INF/services/kong.unirest.core.json.JsonEngine b/unirest-modules-jackson-legacy/src/main/resources/META-INF/services/kong.unirest.core.json.JsonEngine new file mode 100644 index 000000000..99639b806 --- /dev/null +++ b/unirest-modules-jackson-legacy/src/main/resources/META-INF/services/kong.unirest.core.json.JsonEngine @@ -0,0 +1 @@ +kong.unirest.modules.jackson2.JacksonEngine \ No newline at end of file diff --git a/unirest-modules-jackson-legacy/src/test/java/kong/unirest/modules/jackson2/Foo.java b/unirest-modules-jackson-legacy/src/test/java/kong/unirest/modules/jackson2/Foo.java new file mode 100644 index 000000000..da46ca78f --- /dev/null +++ b/unirest-modules-jackson-legacy/src/test/java/kong/unirest/modules/jackson2/Foo.java @@ -0,0 +1,60 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.modules.jackson2; + + + +import java.util.Objects; + +public class Foo { + public String bar; + + public Foo(){ } + + public Foo(String bar) { + this.bar = bar; + } + + @Override + public String toString() { + return "Foo{" + + "bar='" + bar + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) {return true;} + if (o == null || getClass() != o.getClass()) {return false;} + Foo foo = (Foo) o; + return Objects.equals(bar, foo.bar); + } + + @Override + public int hashCode() { + return Objects.hash(bar); + } +} \ No newline at end of file diff --git a/unirest-modules-jackson-legacy/src/test/java/kong/unirest/modules/jackson2/JSONArrayTest.java b/unirest-modules-jackson-legacy/src/test/java/kong/unirest/modules/jackson2/JSONArrayTest.java new file mode 100644 index 000000000..6e0d64e7c --- /dev/null +++ b/unirest-modules-jackson-legacy/src/test/java/kong/unirest/modules/jackson2/JSONArrayTest.java @@ -0,0 +1,545 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.modules.jackson2; + + +import kong.unirest.core.json.JSONArray; +import kong.unirest.core.json.JSONException; +import kong.unirest.core.json.JSONObject; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; + +import java.io.StringWriter; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Collections; + +import static com.google.common.collect.ImmutableMap.of; +import static java.util.Arrays.asList; + +import static org.junit.jupiter.api.Assertions.*; + +class JSONArrayTest { + + @Test + void nullForSoManyReasonsWhenZipping() { + var array = new JSONArray(); + assertNull(array.toJSONObject(new JSONArray(Collections.singletonList("foo")))); + array.put(42L); + assertNull(array.toJSONObject(null)); + assertNull(array.toJSONObject(new JSONArray())); + } + + @Test + void serializeNulls() { + var obj = new JSONArray("[1,null]"); + assertEquals("[1,null]", obj.toString()); + } + + @Test + void exeptionWhileZippingForNull() { + var values = new JSONArray(Arrays.asList(1, "foo", false)); + var names = new JSONArray(); + names.put((String)null); + + JSONException ex = assertThrows(JSONException.class, () -> values.toJSONObject(names)); + assertEquals("JSONArray[0] not a string.", ex.getMessage()); + } + + @Test + void zipAnArray() { + var values = new JSONArray(Arrays.asList(1, "foo", false)); + var names = new JSONArray(Arrays.asList("one", "two", "three", "four")); + JSONObject zipped = values.toJSONObject(names); + assertEquals(1, zipped.get("one")); + assertEquals("foo", zipped.get("two")); + assertEquals(false, zipped.get("three")); + } + + @Test + void putObject() { + var array = new JSONArray(); + array.put(new Foo("fooooo")); + array.put((Object)"abc"); + array.put((Object)new JSONObject(of("foo", "bar"))); + + assertEquals("Foo{bar='fooooo'}", array.get(0).toString()); + assertEquals("abc", array.get(1)); + assertEquals("{\"foo\":\"bar\"}", array.get(2).toString()); + } + + @Test + void simpleConvert() { + String str = "[{\"foo\": \"bar\"}, {\"baz\": 42}]"; + + var array = new JSONArray(str); + + assertEquals(2, array.length()); + assertEquals("bar", array.getJSONObject(0).getString("foo")); + assertEquals(42, array.getJSONObject(1).getInt("baz")); + } + + @Test + @SuppressWarnings("ConstantConditions") + void putObjectAtElement() { + Object nul = null; + Object num = 42; + Object str = "hi"; + Object bool = true; + Object arr = new JSONArray(asList(1,2,3)); + Object obj = new JSONObject(of("f","b")); + + var array = new JSONArray() + .put(5, obj) + .put(4, arr) + .put(3, bool) + .put(2, str) + .put(1, num) + .put(0, nul); + + assertEquals(nul, array.get(0)); + assertEquals(num, array.get(1)); + assertEquals(str, array.get(2)); + assertEquals(bool, array.get(3)); + assertEquals(arr, array.get(4)); + assertEquals(obj, array.get(5)); + } + + @Test + void numbers() { + var obj = new JSONArray(); + assertSame(obj, obj.put((Number)33)); + obj.put("nan"); + + assertEquals(33, obj.getNumber(0)); + assertNotFound(() -> obj.getNumber(5)); + assertNotType(() -> obj.getNumber(1), "JSONArray[1] is not a number."); + + assertEquals(33, obj.optNumber(0)); + assertEquals(66.6d, obj.optNumber(1, 66.6d)); + assertNull(obj.optNumber(5)); + } + + @Test + void doubles() { + var obj = new JSONArray(); + assertSame(obj, obj.put(33.5d)); + obj.put("nan"); + + assertEquals(33.5d, obj.getDouble(0), 4); + assertNotFound(() -> obj.getDouble(5)); + assertNotType(() -> obj.getDouble(1), "JSONArray[1] is not a number."); + + assertEquals(33.5d, obj.optDouble(0), 4); + assertEquals(66.6d, obj.optDouble(1, 66.6d), 4); + assertEquals(Double.NaN, obj.optDouble(5), 4); + } + + @Test + void floats() { + var obj = new JSONArray(); + assertSame(obj, obj.put(33.5f)); + obj.put("nan"); + + assertEquals(33.5f, obj.getFloat(0), 4); + assertNotFound(() -> obj.getFloat(5)); + assertNotType(() -> obj.getFloat(1), "JSONArray[1] is not a number."); + + assertEquals(33.5f, obj.optFloat(0), 4); + assertEquals(66.6f, obj.optFloat(5, 66.6f), 4); + assertEquals(Float.NaN, obj.optFloat(5), 4); + } + + @Test + void longs() { + var obj = new JSONArray(); + assertSame(obj, obj.put(33L)); + obj.put("nan"); + + assertEquals(33L, obj.getLong(0)); + assertNotFound(() -> obj.getLong(5)); + assertNotType(() -> obj.getLong(1), "JSONArray[1] is not a number."); + + assertEquals(33L, obj.optLong(0)); + assertEquals(66L, obj.optLong(5, 66)); + assertEquals(0L, obj.optLong(5)); + } + + @Test + void bools() { + var obj = new JSONArray(); + assertSame(obj, obj.put(true)); + obj.put("nan"); + + assertTrue(obj.getBoolean(0)); + assertNotFound(() -> obj.getBoolean(5)); + assertNotType(() -> obj.getBoolean(1), "JSONArray[1] is not a boolean."); + + assertTrue(obj.optBoolean(0)); + assertTrue(obj.optBoolean(5, true)); + assertFalse(obj.optBoolean(5)); + } + + @Test + void ints() { + var obj = new JSONArray(); + assertSame(obj, obj.put(33)); + obj.put("nan"); + + assertEquals(33, obj.getInt(0)); + assertNotFound(() -> obj.getInt(5)); + assertNotType(() -> obj.getInt(1), "JSONArray[1] is not a number."); + + assertEquals(33, obj.optInt(0)); + assertEquals(66, obj.optInt(5, 66)); + assertEquals(0, obj.optInt(5)); + } + + @Test + void bigInts() { + var obj = new JSONArray(); + assertSame(obj, obj.put(BigInteger.valueOf(33))); + obj.put("nan"); + + assertEquals(BigInteger.valueOf(33), obj.getBigInteger(0)); + assertNotFound(() -> obj.getBigInteger(5)); + assertNotType(() -> obj.getBigInteger(1), "JSONArray[1] is not a number."); + assertEquals(BigInteger.valueOf(33), obj.optBigInteger(0, BigInteger.TEN)); + assertEquals(BigInteger.TEN, obj.optBigInteger(5, BigInteger.TEN)); + } + + @Test + void bigDecimal() { + BigDecimal value = BigDecimal.valueOf(33.5); + var obj = new JSONArray(); + assertSame(obj, obj.put(value)); + obj.put("nan"); + + assertEquals(value, obj.getBigDecimal(0)); + assertNotFound(() -> obj.getBigDecimal(5)); + assertNotType(() -> obj.getBigDecimal(1), "JSONArray[1] is not a number."); + assertEquals(value, obj.optBigDecimal(0, BigDecimal.TEN)); + assertEquals(BigDecimal.TEN, obj.optBigDecimal(5, BigDecimal.TEN)); + } + + @Test + void strings() { + var obj = new JSONArray(); + assertSame(obj, obj.put("cheese")); + obj.put(45); + + assertEquals("cheese", obj.getString(0)); + assertNotFound(() -> obj.getString(5)); + assertEquals("45", obj.getString(1)); + assertEquals("cheese", obj.optString(0)); + assertEquals("logs", obj.optString(5, "logs")); + assertEquals("", obj.optString(5)); + } + + @Test + void jsonObjects() throws Exception { + JSONObject subObj = new JSONObject("{\"derp\": 42}"); + var obj = new JSONArray(); + assertSame(obj, obj.put(subObj)); + obj.put(45); + + JSONObjectTest.assertEqualJson(subObj, obj.getJSONObject(0)); + assertNotFound(() -> obj.getJSONObject(5)); + assertNotType(() -> obj.getJSONObject(1), "JSONArray[1] is not a JSONObject."); + JSONObjectTest.assertEqualJson(subObj, obj.optJSONObject(0)); + assertNull(obj.optJSONObject(5)); + } + + @Test + void jsonArrays() throws Exception { + var subObj = new JSONArray("[42]"); + var obj = new JSONArray(); + assertSame(obj, obj.put(subObj)); + obj.put(45); + + JSONObjectTest.assertEqualJson(subObj, obj.getJSONArray(0)); + assertNotFound(() -> obj.getJSONArray(5)); + assertNotType(() -> obj.getJSONArray(1), "JSONArray[1] is not a JSONArray."); + JSONObjectTest.assertEqualJson(subObj, obj.optJSONArray(0)); + assertNull(obj.optJSONArray(5)); + } + + @Test + void enums() { + var obj = new JSONArray(); + assertSame(obj, obj.put(fruit.orange)); + obj.put("nan"); + + assertEquals(fruit.orange, obj.getEnum(fruit.class, 0)); + assertNotType(() -> obj.getEnum(fruit.class, 1), "JSONArray[1] is not an enum of type \"fruit\"."); + assertEquals(fruit.orange, obj.optEnum(fruit.class, 0)); + assertEquals(fruit.apple, obj.optEnum(fruit.class, 1, fruit.apple)); + assertNull(obj.optEnum(fruit.class, 5)); + } + + @Test + void joinArray() { + String str = "[33.5, 42, \"foo\", true, \"apple\"]"; + + var array = new JSONArray(str); + + assertEquals("33.5, 42, \"foo\", true, \"apple\"", array.join(", ")); + } + + @Test + void toStringIt() { + String str = "[33.5, 42, \"foo\", true, \"apple\"]"; + + var array = new JSONArray(str); + + assertEquals("[33.5,42,\"foo\",true,\"apple\"]", array.toString()); + } + + @Test + void toStringItIndent() { + String str = "[33.5, 42, \"foo\", true, \"apple\"]"; + + var array = new JSONArray(str); + + assertEquals("[ 33.5, 42, \"foo\", true, \"apple\" ]", array.toString(3)); + } + + @Test + void rawGet() { + var array = new JSONArray(asList( + 33.457848383, + 1, + "cheese", + new JSONObject(of("foo", "bar")), + new JSONArray(asList(1,2)))); + + assertTrue(array.get(0) instanceof Double); + assertTrue(array.get(1) instanceof Integer); + assertTrue(array.get(2) instanceof String); + assertTrue(array.get(3) instanceof JSONObject); + assertTrue(array.get(4) instanceof JSONArray); + } + + @Test + void arraysOfArrays() { + String str = "[[1,2,3],[6,7,8]]"; + + var array = new JSONArray(str); + + assertEquals(2, array.getJSONArray(0).get(1)); + assertNull(array.optJSONArray(2)); + } + + @Test + void writer() { + String str = "[1,2,3]"; + + var array = new JSONArray(str); + + StringWriter sw = new StringWriter(); + + array.write(sw); + + assertEquals(str, sw.toString()); + } + + @Test + void writerIndent() { + String str = "[1,2,3]"; + + var array = new JSONArray(str); + + StringWriter sw = new StringWriter(); + + array.write(sw, 3, 3); + + assertEquals("[ 1, 2, 3 ]", sw.toString()); + } + + @Test + void remove() { + var o = new JSONObject(of("foo","bar")); + var array = new JSONArray(asList(1, o)); + + Object remove = array.remove(1); + assertTrue(remove instanceof JSONObject); + assertEquals(o, remove); + assertEquals(1, array.length()); + assertNull(array.remove(55)); + } + + @Test + void removeMissingIndex() { + var array = new JSONArray("[1,2,3]"); + assertNull(array.remove(55)); + } + + @Test + void putSimple() { + var array = new JSONArray(); + array.put(1); + array.put(Long.MAX_VALUE); + array.put(3.5d); + array.put(6.4f); + array.put("howdy"); + array.put(fruit.pear); + array.put(of("foo", 22)); + array.put(asList(1,2,3)); + + assertEquals(1, array.get(0)); + assertEquals(Long.MAX_VALUE, array.get(1)); + assertEquals(3.5d, array.get(2)); + assertEquals(6.4f, ((Double)array.get(3)).floatValue(), 2); + assertEquals("howdy", array.get(4)); + assertEquals("pear", array.get(5)); + assertTrue(new JSONObject(of("foo", 22)).similar(array.get(6))); + assertTrue(new JSONArray(asList(1,2,3)).similar(array.get(7))); + + assertEquals("[1,9223372036854775807,3.5,6.4,\"howdy\",\"pear\",{\"foo\":22},[1,2,3]]", + array.toString()); + } + + @Test + void putByIndex() { + var array = new JSONArray(); + array.put(5, fruit.pear); + array.put(0, 1); + array.put(1, Long.MAX_VALUE); + array.put(2, 3.5d); + array.put(3, 6.4f); + array.put(4, "howdy"); + array.put(6, of("foo", 22)); + array.put(7, asList(1,2,3)); + + assertEquals(1, array.get(0)); + assertEquals(Long.MAX_VALUE, array.get(1)); + assertEquals(3.5d, array.get(2)); + assertEquals(6.4f, ((Double)array.get(3)).floatValue(), 2); + assertEquals("howdy", array.get(4)); + assertEquals("pear", array.get(5)); + assertTrue(new JSONObject(of("foo", 22)).similar(array.get(6))); + assertTrue(new JSONArray(asList(1,2,3)).similar(array.get(7))); + + assertEquals("[1,9223372036854775807,3.5,6.4,\"howdy\",\"pear\",{\"foo\":22},[1,2,3]]", + array.toString()); + } + + @Test + void query() { + var obj = new JSONArray("[{\"a\":{\"b\": 42}}]"); + assertEquals(42, obj.query("/0/a/b")); + } + + @Test + void putCollection() { + var ints = asList(1, 1, 2, 3); + var array = new JSONArray(); + array.put(ints); + + assertEquals(1, array.length()); + assertEquals(ints, array.getJSONArray(0).toList()); + } + + @Test + void constructCollection() { + var ints = asList(1, 1, 2, 3); + var array = new JSONArray(ints); + + assertEquals(4, array.length()); + assertEquals(ints, array.toList()); + } + + @Test + void constructArray() { + var ints = asList(1, 1, 2, 3); + var array = new JSONArray(ints.toArray()); + + assertEquals(4, array.length()); + assertEquals(ints, array.toList()); + } + + @Test + void constructArrayError() { + JSONException ex = assertThrows(JSONException.class, ()-> new JSONArray(new Object())); + assertEquals("JSONArray initial value should be a string or collection or array.", ex.getMessage()); + } + + @Test + void nullMembers() { + var array = new JSONArray(); + array.put("foo"); + array.put((Object) null); + + assertFalse(array.isNull(0)); + assertTrue(array.isNull(1)); + assertTrue(array.isNull(2)); + assertTrue(array.isNull(33)); + } + + @Test + void puttingSomeRandoObjectWillResultInString() { + var array = new JSONArray(); + array.put(new Fudge()); + + assertEquals("[\"Hello World\"]", array.toString()); + } + + @Test + @SuppressWarnings("SuspiciousMethodCalls") + void iterateOverArray() { + var list = asList(1, 2, 3, 4); + var array = new JSONArray(list); + for(Object i : array){ + assertTrue(list.contains(i)); + } + } + + public static void assertNotType(Executable exRunnable, String message) { + JSONException ex = assertThrows(JSONException.class, exRunnable); + assertEquals(message, ex.getMessage()); + } + + private void assertNotFound(Executable exRunnable) { + assertNotType(exRunnable, "JSONArray[5] not found."); + } + + public static class Fudge { + public String foo = "bar"; + + public String toString(){ + return "Hello World"; + } + } + + public enum fruit {orange, apple, pear} + +} + + + + + diff --git a/unirest-modules-jackson-legacy/src/test/java/kong/unirest/modules/jackson2/JSONObjectTest.java b/unirest-modules-jackson-legacy/src/test/java/kong/unirest/modules/jackson2/JSONObjectTest.java new file mode 100644 index 000000000..38a0c84d9 --- /dev/null +++ b/unirest-modules-jackson-legacy/src/test/java/kong/unirest/modules/jackson2/JSONObjectTest.java @@ -0,0 +1,688 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.modules.jackson2; + + +import kong.unirest.core.UnirestException; +import kong.unirest.core.json.*; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; +import org.skyscreamer.jsonassert.JSONAssert; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Map; +import java.util.Set; + +import static com.google.common.collect.ImmutableMap.of; +import static com.google.common.collect.Sets.newHashSet; +import static java.util.Arrays.asList; +import static org.junit.jupiter.api.Assertions.*; + +class JSONObjectTest { + + @Test + void canSimpleSerializeAnObject() { + var test = new TestMe(true, "Wakk Wakka", 42, new TestMe()); + var o = new JSONObject(test); + assertEquals(true, o.optBoolean("aBoolean")); + assertEquals("Wakk Wakka", o.optString("aSillyString")); + assertEquals(42, o.optNumber("aNumber")); + assertNotNull(o.optJSONObject("aSub")); + } + + @Test + void isEmpty() { + var o = new JSONObject(); + assertTrue(o.isEmpty()); + o.put("foo", "bar"); + assertFalse(o.isEmpty()); + } + + @Test + void isNull() { + var o = new JSONObject(); + assertTrue(o.isNull("foo")); + o.put("foo", (Object)null); + assertTrue(o.isNull("foo")); + o.put("foo", 42); + assertFalse(o.isNull("foo")); + } + + @Test + void contructInvalid() { + JSONException ex = assertThrows(JSONException.class, () -> new JSONObject("foo")); + assertEquals("Invalid JSON", ex.getMessage()); + } + + @Test + void simpleConvert() { + String str = "{\"foo\": {\"baz\": 42}}"; + + JSONObject obj = new JSONObject(str); + + assertTrue(obj.has("foo")); + assertEquals(1, obj.length()); + assertEquals(42, obj.getJSONObject("foo").getInt("baz")); + } + + @Test + void doubles() { + JSONObject obj = new JSONObject(); + obj.put("key", 33.5d); + obj.put("not", "nan"); + + assertEquals(33.5d, obj.getDouble("key"), 4); + assertNotFound(() -> obj.getDouble("boo")); + assertJSONEx(() -> obj.getDouble("not"), "JSONObject[\"not\"] is not a number."); + isTypeAndValue(33.5, Double.class, obj.get("key")); + + assertEquals(33.5d, obj.optDouble("key"), 4); + assertEquals(66.6d, obj.optDouble("boo", 66.6d), 4); + assertEquals(Double.NaN, obj.optDouble("boo"), 4); + } + + @Test + void nullsAreSerialized() { + JSONObject obj = new JSONObject("{\"key1\": \"value\", \"key2\": null}"); + + assertEquals("{\"key1\":\"value\",\"key2\":null}", obj.toString()); + } + + @Test + void issue_366() { + JSONObject jsonObject = new JSONObject("{\"status\":\"OK\",\"message\":\"hive_1597818501335\"}"); + assertEquals("{\"status\":\"OK\",\"message\":\"hive_1597818501335\"}", jsonObject.toString()); + } + + @Test + void nullsAreSerializedOnPretty() { + JSONObject obj = new JSONObject("{\"key1\": \"value\", \"key2\": null}"); + + assertEquals("{\n" + + " \"key1\" : \"value\",\n" + + " \"key2\" : null\n" + + "}", obj.toString(3)); + } + + @Test + void floats() { + JSONObject obj = new JSONObject(); + obj.put("key", 33.5f); + obj.put("not", "nan"); + + assertEquals(33.5f, obj.getFloat("key"), 4); + assertNotFound(() -> obj.getFloat("boo")); + assertJSONEx(() -> obj.getFloat("not"), "JSONObject[\"not\"] is not a number."); + isTypeAndValue(33.5, Double.class, obj.get("key")); + + assertEquals(33.5f, obj.optFloat("key"), 4); + assertEquals(66.6f, obj.optFloat("boo", 66.6f), 4); + assertEquals(Float.NaN, obj.optFloat("boo"), 4); + } + + @Test + void longs() { + JSONObject obj = new JSONObject(); + obj.put("key", Long.MAX_VALUE); + obj.put("not", "nan"); + + assertEquals(Long.MAX_VALUE, obj.getLong("key")); + assertNotFound(() -> obj.getLong("boo")); + assertJSONEx(() -> obj.getLong("not"), "JSONObject[\"not\"] is not a number."); + isTypeAndValue(Long.MAX_VALUE, Long.class, obj.get("key")); + + assertEquals(Long.MAX_VALUE, obj.optLong("key")); + assertEquals(66L, obj.optLong("boo", 66)); + assertEquals(0L, obj.optLong("boo")); + } + + @Test + void booleans() { + JSONObject obj = new JSONObject(); + obj.put("key", true); + obj.put("not", "nan"); + + assertTrue(obj.getBoolean("key")); + assertNotFound(() -> obj.getBoolean("boo")); + assertJSONEx(() -> obj.getBoolean("not"), "JSONObject[\"not\"] is not a boolean."); + isTypeAndValue(true, Boolean.class, obj.get("key")); + + assertTrue(obj.optBoolean("key")); + assertTrue(obj.optBoolean("boo", true)); + assertFalse(obj.optBoolean("boo")); + } + + @Test + void ints() { + JSONObject obj = new JSONObject(); + obj.put("key", 33); + obj.put("not", "nan"); + + assertEquals(33, obj.getInt("key")); + assertNotFound(() -> obj.getInt("boo")); + assertJSONEx(() -> obj.getInt("not"), "JSONObject[\"not\"] is not a number."); + isTypeAndValue(33, Integer.class, obj.get("key")); + + assertEquals(33, obj.optInt("key")); + assertEquals(66, obj.optInt("boo", 66)); + assertEquals(0, obj.optInt("boo")); + } + + @Test + void numbers() { + Number tt = 33; + JSONObject obj = new JSONObject(); + obj.put("key", tt); + obj.put("not", "nan"); + + assertEquals(tt, obj.getNumber("key")); + assertNotFound(() -> obj.getNumber("boo")); + assertJSONEx(() -> obj.getNumber("not"), "JSONObject[\"not\"] is not a number."); + isTypeAndValue(tt, Number.class, obj.getNumber("key")); + + assertEquals(tt, obj.optNumber("key")); + assertEquals(66, obj.optNumber("boo", 66)); + assertEquals(0, obj.optNumber("boo")); + } + + @Test + void bigInts() { + JSONObject obj = new JSONObject(); + obj.put("key", BigInteger.valueOf(33)); + obj.put("not", "nan"); + + assertEquals(BigInteger.valueOf(33), obj.getBigInteger("key")); + assertNotFound(() -> obj.getBigInteger("boo")); + assertJSONEx(() -> obj.getBigInteger("not"), "JSONObject[\"not\"] is not a number."); + assertEquals(BigInteger.valueOf(33), obj.optBigInteger("key", BigInteger.TEN)); + assertEquals(BigInteger.TEN, obj.optBigInteger("boo", BigInteger.TEN)); + isTypeAndValue(33, Integer.class, obj.get("key")); + } + + @Test + void bigDecimal() { + BigDecimal value = BigDecimal.valueOf(33.5); + JSONObject obj = new JSONObject(); + obj.put("key", value); + obj.put("not", "nan"); + + assertEquals(value, obj.getBigDecimal("key")); + assertNotFound(() -> obj.getBigDecimal("boo")); + assertJSONEx(() -> obj.getBigDecimal("not"), "JSONObject[\"not\"] is not a number."); + assertEquals(value, obj.optBigDecimal("key", BigDecimal.TEN)); + assertEquals(BigDecimal.TEN, obj.optBigDecimal("boo", BigDecimal.TEN)); + isTypeAndValue(33.5, Double.class, obj.get("key")); + } + + @Test + void strings() { + JSONObject obj = new JSONObject(); + obj.put("key", "cheese"); + obj.put("not", 45); + + assertEquals("cheese", obj.getString("key")); + assertNotFound(() -> obj.getString("boo")); + assertEquals("45", obj.getString("not")); + assertEquals("cheese", obj.optString("key")); + assertEquals("logs", obj.optString("boo", "logs")); + assertEquals("", obj.optString("boo")); + isTypeAndValue("cheese", String.class, obj.get("key")); + } + + @Test + void jsonObjects() throws Exception { + JSONObject subObj = new JSONObject("{\"derp\": 42}"); + JSONObject obj = new JSONObject(); + obj.put("key", subObj); + obj.put("not", 45); + + assertEqualJson(subObj, obj.getJSONObject("key")); + assertNotFound(() -> obj.getJSONObject("boo")); + assertJSONEx(() -> obj.getJSONObject("not"), "JSONObject[\"not\"] is not a JSONObject."); + assertEqualJson(subObj, obj.optJSONObject("key")); + assertNull(obj.optJSONObject("boo")); + assertTrue(subObj.similar(obj.get("key"))); + } + + @Test + void jsonArrays() throws Exception { + var subObj = new JSONArray("[42]"); + JSONObject obj = new JSONObject(); + obj.put("key", subObj); + obj.put("not", 45); + + assertEqualJson(subObj, obj.getJSONArray("key")); + assertNotFound(() -> obj.getJSONArray("boo")); + assertJSONEx(() -> obj.getJSONArray("not"), "JSONObject[\"not\"] is not a JSONArray."); + assertEqualJson(subObj, obj.optJSONArray("key")); + assertNull(obj.optJSONArray("boo")); + assertTrue(subObj.similar(obj.get("key"))); + } + + @Test + void enums() { + JSONObject obj = new JSONObject(); + obj.put("key", fruit.orange); + obj.put("not", "nan"); + + assertEquals(fruit.orange, obj.getEnum(fruit.class, "key")); + assertJSONEx(() -> obj.getEnum(fruit.class, "not"), "JSONObject[\"not\"] is not an enum of type \"fruit\"."); + assertEquals(fruit.orange, obj.optEnum(fruit.class, "key")); + assertEquals(fruit.apple, obj.optEnum(fruit.class, "boo", fruit.apple)); + assertNull(obj.optEnum(fruit.class, "boo")); + isTypeAndValue(obj.get("key"), String.class, "orange"); + } + + @Test + void toStringIt() { + String str = "{\"foo\": 42}"; + + JSONObject obj = new JSONObject(str); + + assertEquals("{\"foo\":42}", obj.toString()); + } + + @Test + void toStringItIndent() { + String str = "{\"foo\": 42, \"bar\": true}"; + + JSONObject obj = new JSONObject(str); + + assertEquals("{\n" + + " \"foo\" : 42,\n" + + " \"bar\" : true\n" + + "}", obj.toString(3)); + } + + @Test + void objProperties() { + String str = "{\"foos\": [6,7,8]}"; + + JSONObject obj = new JSONObject(str); + + assertEquals(7, obj.getJSONArray("foos").get(1)); + assertEquals(7, obj.optJSONArray("foos").get(1)); + assertNull(obj.optJSONArray("bars")); + } + + @Test + void writer() { + String str = "{\"foo\":42}"; + + JSONObject obj = new JSONObject(str); + + StringWriter sw = new StringWriter(); + + obj.write(sw); + + assertEquals(str, sw.toString()); + } + + @Test + void writerIndent() { + String str = "{\"foo\": 42, \"bar\": true}"; + + JSONObject obj = new JSONObject(str); + + StringWriter sw = new StringWriter(); + + obj.write(sw, 3, 3); + + assertEquals("{\n" + + " \"foo\" : 42,\n" + + " \"bar\" : true\n" + + "}", sw.toString()); + } + + @Test + void remove() { + JSONObject obj = new JSONObject("{\"foo\": 42, \"bar\": true}"); + assertEquals(42, obj.remove("foo")); + assertNull(obj.remove("nothing")); + assertEquals("{\"bar\":true}", obj.toString()); + } + + @Test + void removeAThingThatDoesntExist() { + JSONObject obj = new JSONObject(); + obj.remove("foo"); + + assertEquals(0, obj.length()); + } + + @Test + void putReplace() { + JSONObject obj = new JSONObject("{\"bar\": 42}"); + assertEquals(42, obj.get("bar")); + assertSame(obj, obj.put("bar", 33)); + assertEquals(33, obj.get("bar")); + NullPointerException ex = assertThrows(NullPointerException.class, () -> obj.put(null, "hi")); + assertEquals("key == null", ex.getMessage()); + } + + @Test + void accumulateDoesNotCreate() { + JSONObject obj = new JSONObject(); + assertSame(obj, obj.accumulate("bar", 42)); + assertEquals(0, obj.length()); + } + + @Test + void accumulate() { + JSONObject obj = new JSONObject("{\"bar\": 42}"); + assertSame(obj, obj.accumulate("bar", 33)); + assertEquals(2, obj.getJSONArray("bar").length()); + assertEquals(42, obj.getJSONArray("bar").get(0)); + assertEquals(33, obj.getJSONArray("bar").get(1)); + } + + @Test + void accumulateNullKey() { + NullPointerException ex = assertThrows(NullPointerException.class, () -> new JSONObject().accumulate(null, "hi")); + assertEquals("Null key.", ex.getMessage()); + } + + @Test + void append() { + JSONObject obj = new JSONObject(); + assertSame(obj, obj.append("bar", 42)); + obj.append("bar", 33); + assertEquals(2, obj.getJSONArray("bar").length()); + assertEquals(42, obj.getJSONArray("bar").get(0)); + assertEquals(33, obj.getJSONArray("bar").get(1)); + } + + @Test + void appendNullKey() { + NullPointerException ex = assertThrows(NullPointerException.class, () -> new JSONObject().append(null, "hi")); + assertEquals("Null key.", ex.getMessage()); + } + + @Test + void appendToNotAnArrary() { + JSONObject obj = new JSONObject(); + assertSame(obj, obj.put("bar", "not")); + JSONException ex = assertThrows(JSONException.class, () -> obj.append("bar", 33)); + assertEquals("JSONObject[\"bar\"] is not a JSONArray.", ex.getMessage()); + } + + @Test + void increment() { + JSONObject obj = new JSONObject(); + assertSame(obj, obj.increment("cool-beans")); + assertEquals(1, obj.get("cool-beans")); + obj.increment("cool-beans") + .increment("cool-beans") + .increment("cool-beans"); + assertEquals(4, obj.get("cool-beans")); + } + + @Test + void incrementDouble() { + JSONObject obj = new JSONObject(); + assertSame(obj, obj.put("cool-beans", 1.5)); + obj.increment("cool-beans"); + assertEquals(2.5, obj.get("cool-beans")); + } + + + @Test + void putOnce() { + JSONObject obj = new JSONObject(); + assertSame(obj, obj.putOnce("foo", "bar")); + assertJSONEx(() -> obj.putOnce("foo", "baz"), "Duplicate key \"foo\""); + assertEquals("bar", obj.getString("foo")); + } + + @Test + void optPut() { + JSONObject obj = new JSONObject(); + assertSame(obj, obj.putOpt("foo", "bar")); + obj.putOpt(null, "bar"); + obj.putOpt("foo", null); + assertEquals("bar", obj.get("foo")); + obj.putOpt("foo", "qux"); + assertEquals("qux", obj.get("foo")); + } + + @Test + void keySet() { + JSONObject obj = new JSONObject(); + obj.put("one", "a"); + obj.put("two", "b"); + Set exp = newHashSet("one", "two"); + assertEquals(exp, obj.keySet()); + assertEquals(exp, newHashSet(obj.keys())); + } + + @Test + void similar() { + JSONObject obj1 = new JSONObject("{\"foo\":42}"); + JSONObject obj2 = new JSONObject("{\"foo\":42}"); + assertTrue(obj1.similar(obj2)); + obj1.put("foo", -9); + assertFalse(obj1.similar(obj2)); + } + + @Test + void query() { + JSONObject obj = new JSONObject("{\"a\":{\"b\": 42}}"); + assertEquals(42, obj.query("/a/b")); + } + + @Test + void maps() { + JSONObject obj = new JSONObject("{\"foo\": {\"bar\": 42}, \"qux\": 21474836475, \"baz\": 55}"); + + Map map = obj.toMap(); + assertEquals(55, map.get("baz")); + assertEquals(21474836475L, map.get("qux")); + JSONObject sub = (JSONObject) obj.get("foo"); + assertEquals(42, sub.get("bar")); + } + + @Test + void names() { + JSONObject obj = new JSONObject(of("foo", 1, "bar", 2, "baz", 3)); + var names = obj.names(); + assertEquals( + newHashSet("foo", "bar", "baz"), + newHashSet(names.toList()) + ); + } + + @Test + void toJSONArray() { + var o = new JSONObject(of("foo","bar","baz",42)); + + assertNull(o.toJSONArray(new JSONArray())); + + assertEquals(new JSONArray(asList("bar", 42)), + o.toJSONArray(new JSONArray(asList("foo", "baz")))); + + assertEquals(new JSONArray(asList(null, null)), + new JSONObject().toJSONArray(new JSONArray(asList("foo", "baz")))); + } + + @Test + void putCollection() { + var o = new JSONObject(); + o.put("foo", asList(1,2,3)); + assertEquals("{\"foo\":[1,2,3]}", o.toString()); + } + + @Test + void putObjectAsMap() { + var o = new JSONObject(); + o.put("foo", of("baz", 42)); + assertEquals("{\"foo\":{\"baz\":42}}", o.toString()); + } + + @Test + @SuppressWarnings("ConstantConditions") + void stringToValue() { + assertSame(JSONObject.NULL, JSONObject.stringToValue("null")); + assertEquals(true, JSONObject.stringToValue("true")); + assertEquals(false, JSONObject.stringToValue("false")); + assertEquals(42, JSONObject.stringToValue("42")); + assertEquals(45.25, JSONObject.stringToValue("45.25")); + assertEquals(-45.25, JSONObject.stringToValue("-45.25")); + NullPointerException ex = assertThrows(NullPointerException.class, () -> JSONObject.stringToValue(null)); + } + + @Test + void quote() { + assertEquals("\"\\\"foo\\\"hoo\"", JSONObject.quote("\"foo\"hoo")); + } + + @Test + void quoteWriter() throws IOException { + StringWriter w = new StringWriter(); + Writer quote = JSONObject.quote("\"foo\"hoo", w); + assertEquals("\"\\\"foo\\\"hoo\"", quote.toString()); + } + + @Test + void wrapPrimitives() { + assertEquals(42, JSONObject.wrap(42)); + assertEquals(42.5, JSONObject.wrap(42.5)); + assertSame(JSONObject.NULL, JSONObject.wrap(null)); + assertEquals(true, JSONObject.wrap(true)); + } + + @Test + void wrapObjects() { + assertTrue(new JSONArray(asList(1,2,3)).similar(JSONObject.wrap(asList(1,2,3)))); + assertTrue(new JSONArray(asList(1,2,3)).similar(JSONObject.wrap(new int[]{1,2,3}))); + assertTrue(new JSONObject(of("f",1)).similar(JSONObject.wrap(of("f",1)))); + assertTrue(new JSONObject().similar(JSONObject.wrap(new Foo("hi")))); + } + + @Test + void doubleToString() { + assertEquals("42", JSONObject.doubleToString(42)); + assertEquals("42.5643", JSONObject.doubleToString(42.5643)); + } + + @Test + void numberToString() { + assertEquals("42", JSONObject.numberToString(42)); + assertEquals("42.5643", JSONObject.numberToString(42.5643f)); + } + + @Test + void valueToString() { + assertEquals("null", JSONObject.valueToString(null)); + assertEquals("42", JSONObject.valueToString(42)); + assertEquals("42.5643", JSONObject.valueToString(42.5643f)); + assertEquals("\"Hello World\"", JSONObject.valueToString("Hello World")); + assertEquals("{\"bar\":\"me\"}", JSONObject.valueToString(new Foo("me"))); + assertEquals("{}", JSONObject.valueToString(new JSONObject())); + assertEquals("[]", JSONObject.valueToString(new JSONArray())); + } + + @Test + void getNames() { + assertArrayEquals(null, JSONObject.getNames(new JSONObject())); + assertArrayEquals(new String[]{"a","b"}, JSONObject.getNames(new JSONObject(of("a",1,"b",2)))); + } + + private void assertNotFound(Executable exRunnable) { + assertJSONEx(exRunnable, "JSONObject[\"boo\"] not found."); + } + + public static void assertJSONEx(Executable exRunnable, String message) { + JSONException ex = assertThrows(JSONException.class, exRunnable); + assertEquals(message, ex.getMessage()); + } + + public static void assertEqualJson(Object subObj, Object value){ + try { + JSONAssert.assertEquals(subObj.toString(), value.toString(), true); + } catch (org.json.JSONException e) { + throw new UnirestException(e); + } + } + + public static void isTypeAndValue(Object o, Class type, Object value) { + assertEquals(o, value); + assertTrue(type.isInstance(o)); + } + + public enum fruit {orange, apple} + + public static class TestMe { + private boolean aBoolean; + private String aSillyString; + private Integer aNumber; + private TestMe aSub; + + public TestMe(){ } + + public TestMe(boolean bool, String aString, int aNumber, TestMe aSub){ + this.aBoolean = bool; + this.aSillyString = aString; + this.aNumber = aNumber; + this.aSub = aSub; + } + + public boolean isaBoolean() { + return aBoolean; + } + + public void setaBoolean(boolean aBoolean) { + this.aBoolean = aBoolean; + } + + public String getaSillyString() { + return aSillyString; + } + + public void setaSillyString(String aSillyString) { + this.aSillyString = aSillyString; + } + + public Integer getaNumber() { + return aNumber; + } + + public void setaNumber(Integer aNumber) { + this.aNumber = aNumber; + } + + public TestMe getaSub() { + return aSub; + } + + public void setaSub(TestMe aSub) { + this.aSub = aSub; + } + } +} diff --git a/unirest-modules-jackson-legacy/src/test/java/kong/unirest/modules/jackson2/JSONPointerTest.java b/unirest-modules-jackson-legacy/src/test/java/kong/unirest/modules/jackson2/JSONPointerTest.java new file mode 100644 index 000000000..e575637c1 --- /dev/null +++ b/unirest-modules-jackson-legacy/src/test/java/kong/unirest/modules/jackson2/JSONPointerTest.java @@ -0,0 +1,187 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.modules.jackson2; + +import com.google.common.base.Charsets; +import com.google.common.io.Resources; +import kong.unirest.core.json.JSONObject; +import kong.unirest.core.json.JSONPointer; +import kong.unirest.core.json.JSONPointerException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +import static org.junit.jupiter.api.Assertions.*; + +class JSONPointerTest { + // https://tools.ietf.org/html/rfc6901 + private static final String RFC_TEST = getResource("JSON_POINTER_REF.json"); + private final JSONObject obj = new JSONObject(RFC_TEST); + + public static String getResource(String resourceName){ + try { + return Resources.toString(Resources.getResource(resourceName), Charsets.UTF_8); + }catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Test + void nullQuery() { + var ex = assertThrows(NullPointerException.class, () -> obj.query((String)null)); + assertEquals("pointer cannot be null", ex.getMessage()); + } + + @Test + void invalidPathQuery() { + var ex = assertThrows(IllegalArgumentException.class, () -> obj.query("foo")); + assertEquals("a JSON pointer should start with '/' or '#/'", ex.getMessage()); + } + + @Test + void invalidPathQuery_downpath() { + var ex = assertThrows(JSONPointerException.class, () -> obj.query("/shwoop/dedoop")); + assertEquals("Path Segment Missing: shwoop", ex.getMessage()); + } + + @Test + void arrayPartThatDoesNotExist() { + var ex = assertThrows(JSONPointerException.class, () -> obj.query("/foo/5")); + assertEquals("index 5 is out of bounds - the array has 2 elements", ex.getMessage()); + } + + @Test + void referenceAnArrayAsAThing() { + var ex = assertThrows(JSONPointerException.class, () -> obj.query("/foo/bar")); + assertEquals("bar is not an array index", ex.getMessage()); + } + + @Test + @SuppressWarnings("RedundantCast") + void constructorMayNotTakeNull() { + var ex = assertThrows(NullPointerException.class, () -> new JSONPointer((String) null)); + assertEquals("pointer cannot be null", ex.getMessage()); + } + + @Test + void toStringReturnsOriginalString() { + assertEquals("/foo/g~0h/baz", new JSONPointer("/foo/g~h/baz").toString()); + assertEquals("/foo/g~0h/baz", JSONPointer.compile("/foo/g~h/baz").toString()); + } + + @Test + void canGetAsURIFragmanet() { + assertEquals("#/foo/g%7Eh/baz", new JSONPointer("/foo/g~h/baz").toURIFragment()); + } + + @Test + void elementInObjectDoesNotExist() { + assertNull(obj.query("/derpa")); + } + + @Test + void testRef_all() throws Exception { + assertQueryJson(RFC_TEST, ""); + } + + @Test + void testRef_Array() throws Exception { + assertQueryJson("[\"bar\", \"baz\"]", "/foo"); + } + + @Test + void testRef_ArrayZero() { + assertEquals("bar", obj.query("/foo/0").toString()); + } + + @Test + void testRef_Slash() { + assertEquals(0, obj.query("/")); + } + + @Test + void testRef_ab() { + assertEquals(1, obj.query("/a~1b")); + } + + @Test + void testRef_cd() { + assertEquals(2, obj.query("/c%d")); + } + + @Test + void testRef_ef() { + assertEquals(3, obj.query("/e^f")); + } + + @Test + void testRef_gh() { + assertEquals(4, obj.query("/g|h")); + } + + @Test + void testRef_ij() { + assertEquals(5, obj.query("/i\\j")); + } + + @Test + void testRef_kl() { + assertEquals(6, obj.query("/k\"l")); + } + + @Test + void testRef_space() { + assertEquals(7, obj.query("/ ")); + } + + @Test + void testRef_mn() { + assertEquals(8, obj.query("/m~0n")); + } + + @Test + void letsGoDeep() { + assertEquals(true, obj.query("/cucu/0/banana/pants")); + } + + @Test + void builder(){ + JSONPointer pointer = JSONPointer + .builder() + .append("foo") + .append(4) + .append("n~t") + .append("bar/1") + .build(); + + assertEquals(new JSONPointer("/foo/4/n~0t/bar/1").toString(), + pointer.toString()); + } + + private void assertQueryJson(String s, String s2) throws Exception { + JSONAssert.assertEquals(s, obj.query(s2).toString(), true); + } + +} diff --git a/unirest-modules-jackson-legacy/src/test/java/kong/unirest/modules/jackson2/JacksonCoreFactoryTest.java b/unirest-modules-jackson-legacy/src/test/java/kong/unirest/modules/jackson2/JacksonCoreFactoryTest.java new file mode 100644 index 000000000..84dfccb44 --- /dev/null +++ b/unirest-modules-jackson-legacy/src/test/java/kong/unirest/modules/jackson2/JacksonCoreFactoryTest.java @@ -0,0 +1,51 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.modules.jackson2; + +import kong.unirest.core.json.CoreFactory; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class JacksonCoreFactoryTest { + @Test + void canLoadByServiceLocator() { + assertThat(CoreFactory.findEngineWithServiceLocator()) + .isInstanceOf(JacksonEngine.class); + } + + @Test + void canLoadByKnownClassNames() { + assertThat(CoreFactory.findEngineWithClassLoader()) + .isInstanceOf(JacksonEngine.class); + } + + @Test + void willLoadOneWaOrAnother() { + assertThat(CoreFactory.findEngine()) + .isInstanceOf(JacksonEngine.class); + } +} diff --git a/unirest-modules-jackson-legacy/src/test/java/kong/unirest/modules/jackson2/JacksonObjectMapperTest.java b/unirest-modules-jackson-legacy/src/test/java/kong/unirest/modules/jackson2/JacksonObjectMapperTest.java new file mode 100644 index 000000000..549514666 --- /dev/null +++ b/unirest-modules-jackson-legacy/src/test/java/kong/unirest/modules/jackson2/JacksonObjectMapperTest.java @@ -0,0 +1,134 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* +The MIT License + +Copyright for portions of unirest-java are held by Kong Inc (c) 2018. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +package kong.unirest.modules.jackson2; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import kong.unirest.core.GenericType; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class JacksonObjectMapperTest { + private JacksonObjectMapper om = new JacksonObjectMapper(); + + @Test + void canWrite() throws JSONException { + var test = new TestMe("foo", 42, new TestMe("bar", 666, null)); + + String json = om.writeValue(test); + + JSONAssert.assertEquals( + "{\"text\":\"foo\",\"nmbr\":42,\"another\":{\"text\":\"bar\",\"nmbr\":666,\"another\":null}}" + , json + , true + ); + } + + @Test + void canRead(){ + var test = om.readValue("{\"text\":\"foo\",\"nmbr\":42,\"another\":{\"text\":\"bar\",\"nmbr\":666,\"another\":null}}", + TestMe.class); + + assertEquals("foo", test.text); + assertEquals(42, test.nmbr); + assertEquals("bar", test.another.text); + assertEquals(666, test.another.nmbr); + assertEquals(null, test.another.another); + } + + @Test + void canReadGenerics(){ + var testList = om.readValue("[{\"text\":\"foo\",\"nmbr\":42,\"another\":{\"text\":\"bar\",\"nmbr\":666,\"another\":null}}]", + new GenericType>(){}); + + var test = testList.get(0); + + assertEquals("foo", test.text); + assertEquals(42, test.nmbr); + assertEquals("bar", test.another.text); + assertEquals(666, test.another.nmbr); + assertEquals(null, test.another.another); + } + + @Test + void configSoItFails() { + ObjectMapper jom = new ObjectMapper(); + jom.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true); + JacksonObjectMapper j = new JacksonObjectMapper(jom); + + try { + j.readValue("{\"something\": [1,2,3] }", TestMe.class); + fail("Should have thrown"); + }catch (Exception e) { + assertEquals("com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field \"something\" (class kong.unirest.modules.jackson2.JacksonObjectMapperTest$TestMe), not marked as ignorable (3 known properties: \"another\", \"text\", \"nmbr\")\n" + + " at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 16] (through reference chain: kong.unirest.modules.jackson2.JacksonObjectMapperTest$TestMe[\"something\"])", + e.getMessage()); + } + } + + public static class TestMe { + public String text; + public int nmbr; + public TestMe another; + + public TestMe(){} + + public TestMe(String text, Integer nmbr, TestMe another) { + this.text = text; + this.nmbr = nmbr; + this.another = another; + } + } +} \ No newline at end of file diff --git a/unirest-modules-jackson-legacy/src/test/java/kong/unirest/modules/jackson2/JacksonTimeTests.java b/unirest-modules-jackson-legacy/src/test/java/kong/unirest/modules/jackson2/JacksonTimeTests.java new file mode 100644 index 000000000..427defe8d --- /dev/null +++ b/unirest-modules-jackson-legacy/src/test/java/kong/unirest/modules/jackson2/JacksonTimeTests.java @@ -0,0 +1,333 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.modules.jackson2; + +import com.fasterxml.jackson.annotation.JsonInclude; +import kong.unirest.core.json.JSONObject; +import kong.unirest.modules.jackson2.JacksonObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.time.*; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +import static org.junit.jupiter.api.Assertions.*; + +class JacksonTimeTests { + + JacksonObjectMapper om = new JacksonObjectMapper(); + + @BeforeEach + void before() { + TimeZone.setDefault(TimeZone.getTimeZone("UTC")); + } + + @Test + void serializeEverythingNull() { + String actual = om.writeValue(new TestDates()); + assertEquals("{}", actual); + } + + @Test + void serializeDate_iso_with_time() { + var date = getDate("1985-07-03T18:00:00.042Z"); + + String actual = om.writeValue(date); + + assertEquals("{\"date\":489261600042}", actual); + } + + @Test + void serializeDate_iso_no_time() { + var date = getDate(new Date(489196800000L)); + + String actual = om.writeValue(date); + + assertEquals("{\"date\":489196800000}", actual); + } + + @Test + void deserializeDate_null() { + var back = getTestDate("date", null); + + assertNull(back.getDate()); + } + + @Test + void deserializeDate_iso_with_time() { + var back = getTestDate("date", "1985-07-03T18:30:00.042Z"); + + assertEquals(489263400042L, back.getDate().getTime()); + } + + @Test + void deserializeDate_iso_with_NoTime() { + var back = getTestDate("date", "1985-07-03"); + + assertEquals(489196800000L, back.getDate().getTime()); + } + + @Test + void deserializeDate_iso_datetime_noMillies() { + var back = getTestDate("date", "1985-07-03T18:30:00Z"); + + assertEquals(489263400000L, back.getDate().getTime()); + } + + @Test + void deserializeDate_iso_datetime_noSeconds() { + var back = getTestDate("date", "1985-07-03T18:30Z"); + + assertEquals(489263400000L, back.getDate().getTime()); + } + + @Test + void getDatesFromNumbers() { + var back = getTestDate("date", 42); + + assertEquals(new Date(42), back.getDate()); + } + + @Test + void canSerializeCalendar() { + var test = getCalendar("1985-07-03T18:00:00.042Z"); + + String actual = om.writeValue(test); + assertEquals("{\"calendar\":489261600042}", actual); + } + + @Test + @SuppressWarnings("MagicConstant") + void canSerializeCalendar_no_time() { + var cal = GregorianCalendar.getInstance(); + cal.set(1985, 6, 3, 0, 0, 0); + cal.set(Calendar.MILLISECOND, 0); + var test = getCalendar(cal); + + String actual = om.writeValue(test); + assertEquals("{\"calendar\":489196800000}", actual); + } + + @Test + void deserializeCalendar_iso_unixDates() { + var back = getTestDate("calendar", 489263400042L); + + assertEquals(489263400042L, back.getCalendar().getTimeInMillis()); + } + + @Test + void deserializeCalendar_iso_datetime() { + var back = getTestDate("calendar", "1985-07-03T18:30:00.042Z"); + + assertEquals(489263400042L, back.getCalendar().getTimeInMillis()); + } + + @Test + void deserializeCalendar_iso_datetime_noMillies() { + var back = getTestDate("calendar", "1985-07-03T18:30:00Z"); + + assertEquals(489263400000L, back.getCalendar().getTimeInMillis()); + } + + @Test + void deserializeCalendar_iso_datetime_noSeconds() { + var back = getTestDate("calendar", "1985-07-03T18:30Z"); + + assertEquals(489263400000L, back.getCalendar().getTimeInMillis()); + } + + @Test + void deserializeCalendar_iso_date() { + var back = getTestDate("calendar", "1985-07-03"); + + assertEquals(489196800000L, back.getCalendar().getTimeInMillis()); + } + + @Test + void canSerializeZonedDateTimes() { + var test = new TestDates(); + test.setZonedDateTime(ZonedDateTime.parse("1985-07-03T18:00:00.042Z").withFixedOffsetZone()); + + String actual = om.writeValue(test); + assertEquals("{\"zonedDateTime\":489261600.042000000}", actual); + } + + @Test + void deserializeZonedDateTime_iso_datetime() { + var back = getTestDate("zonedDateTime", "1985-07-03T18:30:00.042Z"); + var parse = ZonedDateTime.parse("1985-07-03T18:30:00.042Z"); + + assertEquals(parse, back.getZonedDateTime()); + } + + @Test + void deserializeZonedDateTime_iso_with_offset() { + var back = getTestDate("zonedDateTime", "1985-07-03T18:30:00.042+02:00"); + var parse = ZonedDateTime.parse("1985-07-03T16:30:00.042Z"); + + assertEquals(parse, back.getZonedDateTime()); + } + + @Test + void canSerializeLocalDateTimes() { + var test = new TestDates(); + test.setLocalDateTime(LocalDateTime.parse("1985-07-03T18:00:00.042")); + + String actual = om.writeValue(test); + assertEquals("{\"localDateTime\":[1985,7,3,18,0,0,42000000]}", actual); + } + + @Test + void deserializeLocalDateTime_iso_datetime() { + var back = getTestDate("localDateTime", "1985-07-03T18:00:00.042"); + + assertEquals(LocalDateTime.parse("1985-07-03T18:00:00.042"), back.getLocalDateTime()); + } + + @Test + void canSerializeLocalDate() { + var test = new TestDates(); + test.setLocalDate(LocalDate.parse("1985-07-03")); + + String actual = om.writeValue(test); + assertEquals("{\"localDate\":[1985,7,3]}", actual); + } + + @Test + void deserializeLocalDate_iso_datetime() { + var back = getTestDate("localDate", "1985-07-03T18:00:00.042"); + + assertEquals(LocalDate.parse("1985-07-03"), back.getLocalDate()); + } + + @Test + void deserializeLocalDate_iso_datetime_noTime() { + var back = getTestDate("localDate", "1985-07-03"); + + assertEquals(LocalDate.parse("1985-07-03"), back.getLocalDate()); + } + + @Test + void doNotEscapeHTML() { + var s = new TestString(); + s.test = "it's a && b || c + 1!?"; + + String res = om.writeValue(s); + + assertEquals("{\"test\":\"it's a && b || c + 1!?\"}", res); + } + + public static class TestString { + public String test; + } + + private TestDates getTestDate(String key, Object date) { + return om.readValue(getJson(key, date), TestDates.class); + } + + private String getJson(String key, Object value){ + JSONObject object = new JSONObject(); + object.put(key, value); + return object.toString(); + } + + @SuppressWarnings("SameParameterValue") + private TestDates getDate(String date) { + Date from = Date.from(ZonedDateTime.parse(date).withFixedOffsetZone().toInstant()); + return getDate(from); + } + + private TestDates getDate(Date from) { + var test = new TestDates(); + test.setDate(from); + return test; + } + + @SuppressWarnings("SameParameterValue") + private TestDates getCalendar(String date) { + Calendar from = GregorianCalendar.from(ZonedDateTime.parse(date)); + return getCalendar(from); + } + + private TestDates getCalendar(Calendar from) { + var test = new TestDates(); + test.setCalendar(from); + return test; + } + + @JsonInclude(JsonInclude.Include.NON_EMPTY) + public static class TestDates { + + private Date date; + private Calendar calendar; + private ZonedDateTime zonedDateTime; + private LocalDateTime localDateTime; + private LocalDate localDate; + + public Date getDate() { + return date; + } + + public void setDate(Date date) { + this.date = date; + } + + public Calendar getCalendar() { + return calendar; + } + + public void setCalendar(Calendar calendar) { + this.calendar = calendar; + } + + public ZonedDateTime getZonedDateTime() { + return zonedDateTime; + } + + public void setZonedDateTime(ZonedDateTime zonedDateTime) { + this.zonedDateTime = zonedDateTime; + } + + public LocalDateTime getLocalDateTime() { + return localDateTime; + } + + public void setLocalDateTime(LocalDateTime localDateTime) { + this.localDateTime = localDateTime; + } + + public LocalDate getLocalDate() { + return localDate; + } + + public void setLocalDate(LocalDate localDate) { + this.localDate = localDate; + } + } +} \ No newline at end of file diff --git a/unirest-modules-jackson-legacy/src/test/resources/JSON_POINTER_REF.json b/unirest-modules-jackson-legacy/src/test/resources/JSON_POINTER_REF.json new file mode 100644 index 000000000..9fc119831 --- /dev/null +++ b/unirest-modules-jackson-legacy/src/test/resources/JSON_POINTER_REF.json @@ -0,0 +1,19 @@ +{ + "foo": ["bar", "baz"], + "": 0, + "a/b": 1, + "c%d": 2, + "e^f": 3, + "g|h": 4, + "i\\j": 5, + "k\"l": 6, + " ": 7, + "m~n": 8, + "cucu": [ + { + "banana": { + "pants" : true + } + } + ] +} \ No newline at end of file diff --git a/unirest-modules-jackson/README.md b/unirest-modules-jackson/README.md new file mode 100644 index 000000000..4616c044e --- /dev/null +++ b/unirest-modules-jackson/README.md @@ -0,0 +1,20 @@ +# Jackson Object Mapper for Unirest + +Welcome. This is a Object Mapper for Unirest based on the popular Jackson JSON parser. +Use it like this: + +```java + Unirest.config().setObjectMapper(new JacksonObjectMapper()); +``` + +You may also provide it with your own tools.jackson.databind.ObjectMapper. + +## Install With [Maven](https://mvnrepository.com/artifact/com.konghq/) +``` + + com.konghq + unirest-objectmapper-jackson + 3.0.01 + +``` + diff --git a/unirest-modules-jackson/pom.xml b/unirest-modules-jackson/pom.xml new file mode 100644 index 000000000..6bbe34577 --- /dev/null +++ b/unirest-modules-jackson/pom.xml @@ -0,0 +1,34 @@ + + + 4.0.0 + + + com.konghq + unirest-java-parent + 4.10.3-SNAPSHOT + + + unirest-modules-jackson + unirest-modules-jackson + Jackson 3 based object mapper for Unirest + + + ${project.parent.basedir} + 17 + 17 + + + + + com.konghq + unirest-java-core + ${project.version} + provided + + + tools.jackson.core + jackson-databind + ${jackson.version} + + + diff --git a/unirest-modules-jackson/src/main/java/kong/unirest/modules/jackson/JacksonArray.java b/unirest-modules-jackson/src/main/java/kong/unirest/modules/jackson/JacksonArray.java new file mode 100644 index 000000000..695a97dfd --- /dev/null +++ b/unirest-modules-jackson/src/main/java/kong/unirest/modules/jackson/JacksonArray.java @@ -0,0 +1,145 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.modules.jackson; + +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.node.ArrayNode; +import tools.jackson.databind.node.NullNode; +import kong.unirest.core.json.JsonEngine; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +class JacksonArray extends JacksonElement implements JsonEngine.Array { + JacksonArray(ArrayNode element) { + super(element); + } + + @Override + public int size() { + return element.size(); + } + + @Override + public JsonEngine.Element get(int index) { + validateIndex(index); + return wrap(element.get(index)); + } + + private void validateIndex(int index) { + if(element.size() < index +1){ + throw new IndexOutOfBoundsException(); + } + } + + + @Override + public JsonEngine.Element remove(int index) { + return wrap(element.remove(index)); + } + + @Override + public JsonEngine.Element put(int index, Number number) { + if(number instanceof Integer){ + element.insert(index, (Integer) number); + } else if (number instanceof Double){ + element.insert(index, (Double)number); + } else if (number instanceof BigInteger) { + element.insert(index, (BigInteger) number); + } else if (number instanceof Float){ + element.insert(index, (Float)number); + } else if(number instanceof BigDecimal) { + element.insert(index, (BigDecimal) number); + } + return this; + } + + @Override + public JsonEngine.Element put(int index, String value) { + element.insert(index, value); + return this; + } + + @Override + public JsonEngine.Element put(int index, Boolean value) { + element.insert(index, value); + return this; + } + + @Override + public void add(JsonEngine.Element obj) { + if(obj == null){ + element.add(NullNode.getInstance()); + return; + } + element.add((JsonNode) obj.getEngineElement()); + } + + @Override + public void set(int index, JsonEngine.Element o) { + if(o == null){ + element.set(index, NullNode.getInstance()); + } else { + element.set(index, (JsonNode)o.getEngineElement()); + } + } + + @Override + public void add(Number number) { + if(number instanceof Integer){ + element.add((Integer) number); + } else if (number instanceof Double){ + element.add((Double)number); + } else if (number instanceof Long){ + element.add((Long)number); + } else if (number instanceof BigInteger) { + element.add((BigInteger) number); + } else if (number instanceof Float){ + element.add((Float)number); + } else if(number instanceof BigDecimal) { + element.add((BigDecimal) number); + } + } + + @Override + public void add(String str) { + element.add(str); + } + + @Override + public void add(Boolean bool) { + element.add(bool); + } + + @Override + public String join(String token) { + return StreamSupport.stream(element.spliterator(), false) + .map(String::valueOf) + .collect(Collectors.joining(token)); + } +} diff --git a/unirest-modules-jackson/src/main/java/kong/unirest/modules/jackson/JacksonElement.java b/unirest-modules-jackson/src/main/java/kong/unirest/modules/jackson/JacksonElement.java new file mode 100644 index 000000000..02fe280d5 --- /dev/null +++ b/unirest-modules-jackson/src/main/java/kong/unirest/modules/jackson/JacksonElement.java @@ -0,0 +1,184 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.modules.jackson; + + +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.node.ArrayNode; +import tools.jackson.databind.node.NullNode; +import tools.jackson.databind.node.ObjectNode; +import tools.jackson.databind.node.ValueNode; +import kong.unirest.core.json.*; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Objects; + +class JacksonElement implements JsonEngine.Element { + protected T element; + + JacksonElement(T element){ + this.element = element; + } + + static JsonEngine.Element wrap(JsonNode node) { + if(node == null || node.isNull()){ + return new JacksonPrimitive(NullNode.getInstance()); + } else if(node.isArray()){ + return new JacksonArray((ArrayNode) node); + } else if(node.isObject()){ + return new JacksonObject((ObjectNode)node); + } else if (node.isValueNode()){ + return new JacksonPrimitive((ValueNode)node); + } + return new JacksonPrimitive(NullNode.getInstance()); + } + + @Override + public JsonEngine.Object getAsJsonObject() { + if(element.isObject()) { + return new JacksonObject((ObjectNode) element); + } + throw new IllegalStateException("Not an object"); + } + + @Override + public boolean isJsonNull() { + return element instanceof NullNode; + } + + @Override + public JsonEngine.Primitive getAsJsonPrimitive() { + return new JacksonPrimitive((ValueNode) element); + } + + @Override + public JsonEngine.Array getAsJsonArray() { + if(!element.isArray()){ + throw new IllegalStateException("Not an Array"); + } + return new JacksonArray((ArrayNode)element); + } + + @Override + public float getAsFloat() { + if(!element.isFloat()){ + throw new NumberFormatException("not a float"); + } + return element.floatValue(); + } + + @Override + public double getAsDouble() { + if(!element.isNumber()){ + throw new NumberFormatException("not a double"); + } + return element.asDouble(); + } + + @Override + public String getAsString() { + return element.asString(); + } + + @Override + public long getAsLong() { + if(!element.isLong() && !element.isIntegralNumber()){ + throw new NumberFormatException("not a long"); + } + return element.asLong(); + } + + @Override + public int getAsInt() { + if(!element.isIntegralNumber()) { + throw new NumberFormatException("Not a number"); + } + return element.asInt(); + } + + @Override + public boolean getAsBoolean() { + return element.asBoolean(); + } + + @Override + public BigInteger getAsBigInteger() { + if(!element.isIntegralNumber()) { + throw new NumberFormatException("Not an integer"); + } + return element.bigIntegerValue(); + } + + @Override + public BigDecimal getAsBigDecimal() { + if(!element.isNumber()){ + throw new NumberFormatException("Not a decimal"); + } + return element.decimalValue(); + } + + @Override + public JsonEngine.Primitive getAsPrimitive() { + if(element.isValueNode()){ + return new JacksonPrimitive((ValueNode) element); + } + throw new JSONException("Not a value type"); + } + + @Override + public boolean isJsonArray() { + return element.isArray(); + } + + @Override + public boolean isJsonPrimitive() { + return element.isValueNode(); + } + + @Override + public boolean isJsonObject() { + return element.isObject(); + } + + @Override + public T getEngineElement() { + return (T)element; + } + + @Override + public boolean equals(Object o) { + if (this == o) {return true;} + if (o == null || getClass() != o.getClass()) {return false;} + JacksonElement that = (JacksonElement) o; + return Objects.equals(element, that.element); + } + + @Override + public int hashCode() { + return Objects.hash(element); + } +} diff --git a/unirest-modules-jackson/src/main/java/kong/unirest/modules/jackson/JacksonEngine.java b/unirest-modules-jackson/src/main/java/kong/unirest/modules/jackson/JacksonEngine.java new file mode 100644 index 000000000..8e723edf3 --- /dev/null +++ b/unirest-modules-jackson/src/main/java/kong/unirest/modules/jackson/JacksonEngine.java @@ -0,0 +1,221 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.modules.jackson; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import kong.unirest.core.ObjectMapper; +import kong.unirest.core.UnirestException; +import kong.unirest.core.json.JSONElement; +import kong.unirest.core.json.JSONException; +import kong.unirest.core.json.JsonEngine; +import tools.jackson.core.JacksonException; +import tools.jackson.core.json.JsonReadFeature; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.databind.node.*; + +import java.io.Writer; +import java.math.BigInteger; +import java.util.Collection; + +public class JacksonEngine implements JsonEngine { + private tools.jackson.databind.ObjectMapper om; + private ObjectMapper objm; + + public JacksonEngine(){ + objm = new JacksonObjectMapper(); + om = JsonMapper.builderWithJackson2Defaults() + .enable(JsonReadFeature.ALLOW_UNQUOTED_PROPERTY_NAMES) + .changeDefaultVisibility(vc -> vc.withFieldVisibility(JsonAutoDetect.Visibility.ANY)) + .build(); + } + + @Override + public String toPrettyJson(Element obj) { + try { + return om.writerWithDefaultPrettyPrinter() + .writeValueAsString(obj.getEngineElement()); + } catch (JacksonException e) { + throw new UnirestException(e); + } + } + + @Override + public String toJson(Element obj) { + try { + return om.writeValueAsString(obj.getEngineElement()); + } catch (JacksonException e) { + throw new UnirestException(e); + } + } + + @Override + public void toJson(Element obj, Writer sw) { + try { + om.writeValue(sw, obj.getEngineElement()); + } catch (JacksonException e) { + throw new JSONException(e); + } + } + + @Override + public void toPrettyJson(Element obj, Writer sw) { + try { + om.writerWithDefaultPrettyPrinter() + .writeValue(sw, obj.getEngineElement()); + } catch (JacksonException e) { + throw new JSONException(e); + } + } + + @Override + public Element toJsonTree(java.lang.Object obj) { + return JacksonElement.wrap(om.valueToTree(obj)); + } + + @Override + public Object newEngineObject() { + return new JacksonObject(om.createObjectNode()); + } + + @Override + public Object newEngineObject(String string) throws JSONException { + try { + return new JacksonObject(om.readValue(string, ObjectNode.class)); + } catch (JacksonException e) { + throw new JSONException("Invalid JSON"); + } + } + + @Override + public Array newJsonArray(String jsonString) throws JSONException { + try { + return new JacksonArray(om.readValue(jsonString, ArrayNode.class)); + } catch (JacksonException e) { + throw new JSONException("Invalid JSON"); + } + } + + @Override + public Array newJsonArray(Collection collection) { + JacksonArray a = new JacksonArray(om.createArrayNode()); + for(java.lang.Object o : collection){ + add(a, o); + } + return a; + } + + private void add(JacksonArray a, java.lang.Object o) { + if(o instanceof Number){ + a.add((Number) o); + } else if (o instanceof String) { + a.add((String) o); + }else if (o instanceof Boolean) { + a.add((Boolean) o); + }else if(o instanceof JSONElement) { + a.add(((JSONElement) o).getElement()); + } else if(o instanceof Element) { + a.add((Element) o); + } else { + JsonNode tree = om.convertValue(o, JsonNode.class); + a.add(JacksonElement.wrap(tree)); + } + } + + @Override + public Array newEngineArray() { + return new JacksonArray(om.createArrayNode()); + } + + @Override + public T fromJson(Element obj, Class mapClass) { + return om.convertValue(obj.getEngineElement(), mapClass); + } + + @Override + public Primitive newJsonPrimitive(T enumValue) { + if (enumValue == null){ + return new JacksonPrimitive(NullNode.getInstance()); + } + return newJsonPrimitive(enumValue.name()); + } + + @Override + public Primitive newJsonPrimitive(String string) { + return convert(string, StringNode::new); + } + + @Override + public Primitive newJsonPrimitive(Number number) { + if(number instanceof Integer) { + return convert((Integer) number, IntNode::new); + }else if (number instanceof Long){ + return convert((Long)number, LongNode::new); + } else if (number instanceof Double){ + return convert((Double)number, DoubleNode::new); + } else if (number instanceof BigInteger) { + return convert((BigInteger)number, BigIntegerNode::new); + } else if (number instanceof Float){ + return convert((Float)number, FloatNode::new); + } + return new JacksonPrimitive(NullNode.getInstance()); + } + + @Override + public Primitive newJsonPrimitive(Boolean bool) { + return convert(bool, BooleanNode::valueOf); + } + + @Override + public ObjectMapper getObjectMapper() { + return objm; + } + + @Override + public String quote(java.lang.Object s) { + try { + return om.writeValueAsString(s); + } catch (JacksonException e) { + throw new JSONException(e); + } + } + + @FunctionalInterface + private interface ValueSupplier { + ValueNode getIt(V value); + } + + private Primitive convert(T value, ValueSupplier supplier){ + try { + if (value == null){ + return new JacksonPrimitive(NullNode.getInstance()); + } + return new JacksonPrimitive(supplier.getIt(value)); + } catch (JacksonException e) { + throw new UnirestException(e); + } + } +} diff --git a/unirest-modules-jackson/src/main/java/kong/unirest/modules/jackson/JacksonObject.java b/unirest-modules-jackson/src/main/java/kong/unirest/modules/jackson/JacksonObject.java new file mode 100644 index 000000000..4f8cb8e2b --- /dev/null +++ b/unirest-modules-jackson/src/main/java/kong/unirest/modules/jackson/JacksonObject.java @@ -0,0 +1,123 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.modules.jackson; + +import tools.jackson.databind.node.NullNode; +import tools.jackson.databind.node.ObjectNode; +import kong.unirest.core.json.JsonEngine; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Map; +import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +class JacksonObject extends JacksonElement implements JsonEngine.Object { + + public JacksonObject(ObjectNode element) { + super(element); + } + + @Override + public int size() { + return element.size(); + } + + @Override + public boolean has(String key) { + return element.has(key); + } + + @Override + public JsonEngine.Element get(String key) { + return wrap(element.get(key)); + } + + @Override + public void add(String key, JsonEngine.Element value) { + element.set(key, value.getEngineElement()); + } + + @Override + public void addProperty(String key, Boolean value) { + element.put(key, value); + } + + @Override + public void addProperty(String key, String value) { + element.put(key, value); + } + + @Override + public void addProperty(String key, Number number) { + if(number instanceof Integer){ + element.put(key, (Integer) number); + } else if (number instanceof Double){ + element.put(key, (Double)number); + } else if (number instanceof BigInteger) { + element.put(key, (BigInteger) number); + } else if (number instanceof Float){ + element.put(key, (Float)number); + } else if(number instanceof BigDecimal) { + element.put(key, (BigDecimal) number); + } else if (number instanceof Long) { + element.put(key, (Long)number); + } + } + + @Override + public void addProperty(String key, JsonEngine.Element value) { + element.set(key, value.getEngineElement()); + } + + @Override + public void remove(String key) { + element.remove(key); + } + + @Override + public void add(String key, E enumValue) { + if(enumValue == null){ + element.set(key, NullNode.getInstance()); + } else { + element.put(key, enumValue.name()); + } + } + + @Override + public Set keySet() { + return StreamSupport.stream( + Spliterators.spliteratorUnknownSize( + element.properties().stream().map(Map.Entry::getKey).collect(Collectors.toSet()).iterator(), + Spliterator.ORDERED) + , false) + .collect(Collectors.toSet()); + + } +} diff --git a/unirest-modules-jackson/src/main/java/kong/unirest/modules/jackson/JacksonObjectMapper.java b/unirest-modules-jackson/src/main/java/kong/unirest/modules/jackson/JacksonObjectMapper.java new file mode 100644 index 000000000..f55d11198 --- /dev/null +++ b/unirest-modules-jackson/src/main/java/kong/unirest/modules/jackson/JacksonObjectMapper.java @@ -0,0 +1,116 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* +The MIT License + +Copyright for portions of unirest-java are held by Kong Inc (c) 2018. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +package kong.unirest.modules.jackson; + +import tools.jackson.core.JacksonException; +import tools.jackson.core.StreamWriteFeature; +import tools.jackson.databind.DeserializationFeature; +import kong.unirest.core.GenericType; +import kong.unirest.core.ObjectMapper; +import kong.unirest.core.UnirestException; +import tools.jackson.databind.json.JsonMapper; + +import java.util.function.Consumer; + + +public class JacksonObjectMapper implements ObjectMapper { + private tools.jackson.databind.ObjectMapper om; + + public JacksonObjectMapper(){ + this(c -> {}); + } + + /** + * Pass in any additional JsonMapper configurations you want + * @param configurations consumer of configurations to perform on the tools.jackson.databind.JsonMapper.Builder + */ + public JacksonObjectMapper(Consumer configurations) { + JsonMapper.Builder builder = JsonMapper.builderWithJackson2Defaults(); + builder.configure(StreamWriteFeature.IGNORE_UNKNOWN, true); + builder.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + configurations.accept(builder); + this.om = builder.build(); + } + + public JacksonObjectMapper(tools.jackson.databind.ObjectMapper om){ + this.om = om; + } + + public tools.jackson.databind.ObjectMapper getJacksonMapper(){ + return om; + } + + @Override + public T readValue(String value, Class valueType) { + try { + return om.readValue(value, valueType); + } catch (JacksonException e) { + throw new UnirestException(e); + } + } + + @Override + public T readValue(String value, GenericType genericType) { + try { + return om.readValue(value, om.constructType(genericType.getType())); + } catch (JacksonException e) { + throw new UnirestException(e); + } + } + + @Override + public String writeValue(Object value) { + try { + return om.writeValueAsString(value); + } catch (JacksonException e) { + throw new UnirestException(e); + } + } +} diff --git a/unirest-modules-jackson/src/main/java/kong/unirest/modules/jackson/JacksonPrimitive.java b/unirest-modules-jackson/src/main/java/kong/unirest/modules/jackson/JacksonPrimitive.java new file mode 100644 index 000000000..c4953ecf7 --- /dev/null +++ b/unirest-modules-jackson/src/main/java/kong/unirest/modules/jackson/JacksonPrimitive.java @@ -0,0 +1,45 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.modules.jackson; + +import tools.jackson.databind.node.ValueNode; +import kong.unirest.core.json.JsonEngine; + +class JacksonPrimitive extends JacksonElement implements JsonEngine.Primitive { + public JacksonPrimitive(ValueNode element) { + super(element); + } + + @Override + public boolean isBoolean() { + return element.isBoolean(); + } + + @Override + public boolean isNumber() { + return element.isNumber(); + } +} diff --git a/unirest-modules-jackson/src/main/resources/META-INF/services/kong.unirest.core.json.JsonEngine b/unirest-modules-jackson/src/main/resources/META-INF/services/kong.unirest.core.json.JsonEngine new file mode 100644 index 000000000..09904cb07 --- /dev/null +++ b/unirest-modules-jackson/src/main/resources/META-INF/services/kong.unirest.core.json.JsonEngine @@ -0,0 +1 @@ +kong.unirest.modules.jackson.JacksonEngine \ No newline at end of file diff --git a/unirest-modules-jackson/src/test/java/kong/unirest/modules/jackson/Foo.java b/unirest-modules-jackson/src/test/java/kong/unirest/modules/jackson/Foo.java new file mode 100644 index 000000000..81c2c357b --- /dev/null +++ b/unirest-modules-jackson/src/test/java/kong/unirest/modules/jackson/Foo.java @@ -0,0 +1,60 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.modules.jackson; + + + +import java.util.Objects; + +public class Foo { + public String bar; + + public Foo(){ } + + public Foo(String bar) { + this.bar = bar; + } + + @Override + public String toString() { + return "Foo{" + + "bar='" + bar + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) {return true;} + if (o == null || getClass() != o.getClass()) {return false;} + Foo foo = (Foo) o; + return Objects.equals(bar, foo.bar); + } + + @Override + public int hashCode() { + return Objects.hash(bar); + } +} \ No newline at end of file diff --git a/unirest-modules-jackson/src/test/java/kong/unirest/modules/jackson/JSONArrayTest.java b/unirest-modules-jackson/src/test/java/kong/unirest/modules/jackson/JSONArrayTest.java new file mode 100644 index 000000000..81271de6f --- /dev/null +++ b/unirest-modules-jackson/src/test/java/kong/unirest/modules/jackson/JSONArrayTest.java @@ -0,0 +1,545 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.modules.jackson; + + +import kong.unirest.core.json.JSONArray; +import kong.unirest.core.json.JSONException; +import kong.unirest.core.json.JSONObject; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; + +import java.io.StringWriter; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Collections; + +import static com.google.common.collect.ImmutableMap.of; +import static java.util.Arrays.asList; + +import static org.junit.jupiter.api.Assertions.*; + +class JSONArrayTest { + + @Test + void nullForSoManyReasonsWhenZipping() { + var array = new JSONArray(); + assertNull(array.toJSONObject(new JSONArray(Collections.singletonList("foo")))); + array.put(42L); + assertNull(array.toJSONObject(null)); + assertNull(array.toJSONObject(new JSONArray())); + } + + @Test + void serializeNulls() { + var obj = new JSONArray("[1,null]"); + assertEquals("[1,null]", obj.toString()); + } + + @Test + void exeptionWhileZippingForNull() { + var values = new JSONArray(Arrays.asList(1, "foo", false)); + var names = new JSONArray(); + names.put((String)null); + + JSONException ex = assertThrows(JSONException.class, () -> values.toJSONObject(names)); + assertEquals("JSONArray[0] not a string.", ex.getMessage()); + } + + @Test + void zipAnArray() { + var values = new JSONArray(Arrays.asList(1, "foo", false)); + var names = new JSONArray(Arrays.asList("one", "two", "three", "four")); + JSONObject zipped = values.toJSONObject(names); + assertEquals(1, zipped.get("one")); + assertEquals("foo", zipped.get("two")); + assertEquals(false, zipped.get("three")); + } + + @Test + void putObject() { + var array = new JSONArray(); + array.put(new Foo("fooooo")); + array.put((Object)"abc"); + array.put((Object)new JSONObject(of("foo", "bar"))); + + assertEquals("Foo{bar='fooooo'}", array.get(0).toString()); + assertEquals("abc", array.get(1)); + assertEquals("{\"foo\":\"bar\"}", array.get(2).toString()); + } + + @Test + void simpleConvert() { + String str = "[{\"foo\": \"bar\"}, {\"baz\": 42}]"; + + var array = new JSONArray(str); + + assertEquals(2, array.length()); + assertEquals("bar", array.getJSONObject(0).getString("foo")); + assertEquals(42, array.getJSONObject(1).getInt("baz")); + } + + @Test + @SuppressWarnings("ConstantConditions") + void putObjectAtElement() { + Object nul = null; + Object num = 42; + Object str = "hi"; + Object bool = true; + Object arr = new JSONArray(asList(1,2,3)); + Object obj = new JSONObject(of("f","b")); + + var array = new JSONArray() + .put(5, obj) + .put(4, arr) + .put(3, bool) + .put(2, str) + .put(1, num) + .put(0, nul); + + assertEquals(nul, array.get(0)); + assertEquals(num, array.get(1)); + assertEquals(str, array.get(2)); + assertEquals(bool, array.get(3)); + assertEquals(arr, array.get(4)); + assertEquals(obj, array.get(5)); + } + + @Test + void numbers() { + var obj = new JSONArray(); + assertSame(obj, obj.put((Number)33)); + obj.put("nan"); + + assertEquals(33, obj.getNumber(0)); + assertNotFound(() -> obj.getNumber(5)); + assertNotType(() -> obj.getNumber(1), "JSONArray[1] is not a number."); + + assertEquals(33, obj.optNumber(0)); + assertEquals(66.6d, obj.optNumber(1, 66.6d)); + assertNull(obj.optNumber(5)); + } + + @Test + void doubles() { + var obj = new JSONArray(); + assertSame(obj, obj.put(33.5d)); + obj.put("nan"); + + assertEquals(33.5d, obj.getDouble(0), 4); + assertNotFound(() -> obj.getDouble(5)); + assertNotType(() -> obj.getDouble(1), "JSONArray[1] is not a number."); + + assertEquals(33.5d, obj.optDouble(0), 4); + assertEquals(66.6d, obj.optDouble(1, 66.6d), 4); + assertEquals(Double.NaN, obj.optDouble(5), 4); + } + + @Test + void floats() { + var obj = new JSONArray(); + assertSame(obj, obj.put(33.5f)); + obj.put("nan"); + + assertEquals(33.5f, obj.getFloat(0), 4); + assertNotFound(() -> obj.getFloat(5)); + assertNotType(() -> obj.getFloat(1), "JSONArray[1] is not a number."); + + assertEquals(33.5f, obj.optFloat(0), 4); + assertEquals(66.6f, obj.optFloat(5, 66.6f), 4); + assertEquals(Float.NaN, obj.optFloat(5), 4); + } + + @Test + void longs() { + var obj = new JSONArray(); + assertSame(obj, obj.put(33L)); + obj.put("nan"); + + assertEquals(33L, obj.getLong(0)); + assertNotFound(() -> obj.getLong(5)); + assertNotType(() -> obj.getLong(1), "JSONArray[1] is not a number."); + + assertEquals(33L, obj.optLong(0)); + assertEquals(66L, obj.optLong(5, 66)); + assertEquals(0L, obj.optLong(5)); + } + + @Test + void bools() { + var obj = new JSONArray(); + assertSame(obj, obj.put(true)); + obj.put("nan"); + + assertTrue(obj.getBoolean(0)); + assertNotFound(() -> obj.getBoolean(5)); + assertNotType(() -> obj.getBoolean(1), "JSONArray[1] is not a boolean."); + + assertTrue(obj.optBoolean(0)); + assertTrue(obj.optBoolean(5, true)); + assertFalse(obj.optBoolean(5)); + } + + @Test + void ints() { + var obj = new JSONArray(); + assertSame(obj, obj.put(33)); + obj.put("nan"); + + assertEquals(33, obj.getInt(0)); + assertNotFound(() -> obj.getInt(5)); + assertNotType(() -> obj.getInt(1), "JSONArray[1] is not a number."); + + assertEquals(33, obj.optInt(0)); + assertEquals(66, obj.optInt(5, 66)); + assertEquals(0, obj.optInt(5)); + } + + @Test + void bigInts() { + var obj = new JSONArray(); + assertSame(obj, obj.put(BigInteger.valueOf(33))); + obj.put("nan"); + + assertEquals(BigInteger.valueOf(33), obj.getBigInteger(0)); + assertNotFound(() -> obj.getBigInteger(5)); + assertNotType(() -> obj.getBigInteger(1), "JSONArray[1] is not a number."); + assertEquals(BigInteger.valueOf(33), obj.optBigInteger(0, BigInteger.TEN)); + assertEquals(BigInteger.TEN, obj.optBigInteger(5, BigInteger.TEN)); + } + + @Test + void bigDecimal() { + BigDecimal value = BigDecimal.valueOf(33.5); + var obj = new JSONArray(); + assertSame(obj, obj.put(value)); + obj.put("nan"); + + assertEquals(value, obj.getBigDecimal(0)); + assertNotFound(() -> obj.getBigDecimal(5)); + assertNotType(() -> obj.getBigDecimal(1), "JSONArray[1] is not a number."); + assertEquals(value, obj.optBigDecimal(0, BigDecimal.TEN)); + assertEquals(BigDecimal.TEN, obj.optBigDecimal(5, BigDecimal.TEN)); + } + + @Test + void strings() { + var obj = new JSONArray(); + assertSame(obj, obj.put("cheese")); + obj.put(45); + + assertEquals("cheese", obj.getString(0)); + assertNotFound(() -> obj.getString(5)); + assertEquals("45", obj.getString(1)); + assertEquals("cheese", obj.optString(0)); + assertEquals("logs", obj.optString(5, "logs")); + assertEquals("", obj.optString(5)); + } + + @Test + void jsonObjects() throws Exception { + JSONObject subObj = new JSONObject("{\"derp\": 42}"); + var obj = new JSONArray(); + assertSame(obj, obj.put(subObj)); + obj.put(45); + + JSONObjectTest.assertEqualJson(subObj, obj.getJSONObject(0)); + assertNotFound(() -> obj.getJSONObject(5)); + assertNotType(() -> obj.getJSONObject(1), "JSONArray[1] is not a JSONObject."); + JSONObjectTest.assertEqualJson(subObj, obj.optJSONObject(0)); + assertNull(obj.optJSONObject(5)); + } + + @Test + void jsonArrays() throws Exception { + var subObj = new JSONArray("[42]"); + var obj = new JSONArray(); + assertSame(obj, obj.put(subObj)); + obj.put(45); + + JSONObjectTest.assertEqualJson(subObj, obj.getJSONArray(0)); + assertNotFound(() -> obj.getJSONArray(5)); + assertNotType(() -> obj.getJSONArray(1), "JSONArray[1] is not a JSONArray."); + JSONObjectTest.assertEqualJson(subObj, obj.optJSONArray(0)); + assertNull(obj.optJSONArray(5)); + } + + @Test + void enums() { + var obj = new JSONArray(); + assertSame(obj, obj.put(fruit.orange)); + obj.put("nan"); + + assertEquals(fruit.orange, obj.getEnum(fruit.class, 0)); + assertNotType(() -> obj.getEnum(fruit.class, 1), "JSONArray[1] is not an enum of type \"fruit\"."); + assertEquals(fruit.orange, obj.optEnum(fruit.class, 0)); + assertEquals(fruit.apple, obj.optEnum(fruit.class, 1, fruit.apple)); + assertNull(obj.optEnum(fruit.class, 5)); + } + + @Test + void joinArray() { + String str = "[33.5, 42, \"foo\", true, \"apple\"]"; + + var array = new JSONArray(str); + + assertEquals("33.5, 42, \"foo\", true, \"apple\"", array.join(", ")); + } + + @Test + void toStringIt() { + String str = "[33.5, 42, \"foo\", true, \"apple\"]"; + + var array = new JSONArray(str); + + assertEquals("[33.5,42,\"foo\",true,\"apple\"]", array.toString()); + } + + @Test + void toStringItIndent() { + String str = "[33.5, 42, \"foo\", true, \"apple\"]"; + + var array = new JSONArray(str); + + assertEquals("[ 33.5, 42, \"foo\", true, \"apple\" ]", array.toString(3)); + } + + @Test + void rawGet() { + var array = new JSONArray(asList( + 33.457848383, + 1, + "cheese", + new JSONObject(of("foo", "bar")), + new JSONArray(asList(1,2)))); + + assertTrue(array.get(0) instanceof Double); + assertTrue(array.get(1) instanceof Integer); + assertTrue(array.get(2) instanceof String); + assertTrue(array.get(3) instanceof JSONObject); + assertTrue(array.get(4) instanceof JSONArray); + } + + @Test + void arraysOfArrays() { + String str = "[[1,2,3],[6,7,8]]"; + + var array = new JSONArray(str); + + assertEquals(2, array.getJSONArray(0).get(1)); + assertNull(array.optJSONArray(2)); + } + + @Test + void writer() { + String str = "[1,2,3]"; + + var array = new JSONArray(str); + + StringWriter sw = new StringWriter(); + + array.write(sw); + + assertEquals(str, sw.toString()); + } + + @Test + void writerIndent() { + String str = "[1,2,3]"; + + var array = new JSONArray(str); + + StringWriter sw = new StringWriter(); + + array.write(sw, 3, 3); + + assertEquals("[ 1, 2, 3 ]", sw.toString()); + } + + @Test + void remove() { + var o = new JSONObject(of("foo","bar")); + var array = new JSONArray(asList(1, o)); + + Object remove = array.remove(1); + assertTrue(remove instanceof JSONObject); + assertEquals(o, remove); + assertEquals(1, array.length()); + assertNull(array.remove(55)); + } + + @Test + void removeMissingIndex() { + var array = new JSONArray("[1,2,3]"); + assertNull(array.remove(55)); + } + + @Test + void putSimple() { + var array = new JSONArray(); + array.put(1); + array.put(Long.MAX_VALUE); + array.put(3.5d); + array.put(6.4f); + array.put("howdy"); + array.put(fruit.pear); + array.put(of("foo", 22)); + array.put(asList(1,2,3)); + + assertEquals(1, array.get(0)); + assertEquals(Long.MAX_VALUE, array.get(1)); + assertEquals(3.5d, array.get(2)); + assertEquals(6.4f, ((Double)array.get(3)).floatValue(), 2); + assertEquals("howdy", array.get(4)); + assertEquals("pear", array.get(5)); + assertTrue(new JSONObject(of("foo", 22)).similar(array.get(6))); + assertTrue(new JSONArray(asList(1,2,3)).similar(array.get(7))); + + assertEquals("[1,9223372036854775807,3.5,6.4,\"howdy\",\"pear\",{\"foo\":22},[1,2,3]]", + array.toString()); + } + + @Test + void putByIndex() { + var array = new JSONArray(); + array.put(5, fruit.pear); + array.put(0, 1); + array.put(1, Long.MAX_VALUE); + array.put(2, 3.5d); + array.put(3, 6.4f); + array.put(4, "howdy"); + array.put(6, of("foo", 22)); + array.put(7, asList(1,2,3)); + + assertEquals(1, array.get(0)); + assertEquals(Long.MAX_VALUE, array.get(1)); + assertEquals(3.5d, array.get(2)); + assertEquals(6.4f, ((Double)array.get(3)).floatValue(), 2); + assertEquals("howdy", array.get(4)); + assertEquals("pear", array.get(5)); + assertTrue(new JSONObject(of("foo", 22)).similar(array.get(6))); + assertTrue(new JSONArray(asList(1,2,3)).similar(array.get(7))); + + assertEquals("[1,9223372036854775807,3.5,6.4,\"howdy\",\"pear\",{\"foo\":22},[1,2,3]]", + array.toString()); + } + + @Test + void query() { + var obj = new JSONArray("[{\"a\":{\"b\": 42}}]"); + assertEquals(42, obj.query("/0/a/b")); + } + + @Test + void putCollection() { + var ints = asList(1, 1, 2, 3); + var array = new JSONArray(); + array.put(ints); + + assertEquals(1, array.length()); + assertEquals(ints, array.getJSONArray(0).toList()); + } + + @Test + void constructCollection() { + var ints = asList(1, 1, 2, 3); + var array = new JSONArray(ints); + + assertEquals(4, array.length()); + assertEquals(ints, array.toList()); + } + + @Test + void constructArray() { + var ints = asList(1, 1, 2, 3); + var array = new JSONArray(ints.toArray()); + + assertEquals(4, array.length()); + assertEquals(ints, array.toList()); + } + + @Test + void constructArrayError() { + JSONException ex = assertThrows(JSONException.class, ()-> new JSONArray(new Object())); + assertEquals("JSONArray initial value should be a string or collection or array.", ex.getMessage()); + } + + @Test + void nullMembers() { + var array = new JSONArray(); + array.put("foo"); + array.put((Object) null); + + assertFalse(array.isNull(0)); + assertTrue(array.isNull(1)); + assertTrue(array.isNull(2)); + assertTrue(array.isNull(33)); + } + + @Test + void puttingSomeRandoObjectWillResultInString() { + var array = new JSONArray(); + array.put(new Fudge()); + + assertEquals("[\"Hello World\"]", array.toString()); + } + + @Test + @SuppressWarnings("SuspiciousMethodCalls") + void iterateOverArray() { + var list = asList(1, 2, 3, 4); + var array = new JSONArray(list); + for(Object i : array){ + assertTrue(list.contains(i)); + } + } + + public static void assertNotType(Executable exRunnable, String message) { + JSONException ex = assertThrows(JSONException.class, exRunnable); + assertEquals(message, ex.getMessage()); + } + + private void assertNotFound(Executable exRunnable) { + assertNotType(exRunnable, "JSONArray[5] not found."); + } + + public static class Fudge { + public String foo = "bar"; + + public String toString(){ + return "Hello World"; + } + } + + public enum fruit {orange, apple, pear} + +} + + + + + diff --git a/unirest-modules-jackson/src/test/java/kong/unirest/modules/jackson/JSONObjectTest.java b/unirest-modules-jackson/src/test/java/kong/unirest/modules/jackson/JSONObjectTest.java new file mode 100644 index 000000000..eda44e8bb --- /dev/null +++ b/unirest-modules-jackson/src/test/java/kong/unirest/modules/jackson/JSONObjectTest.java @@ -0,0 +1,688 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.modules.jackson; + + +import kong.unirest.core.UnirestException; +import kong.unirest.core.json.*; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; +import org.skyscreamer.jsonassert.JSONAssert; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Map; +import java.util.Set; + +import static com.google.common.collect.ImmutableMap.of; +import static com.google.common.collect.Sets.newHashSet; +import static java.util.Arrays.asList; +import static org.junit.jupiter.api.Assertions.*; + +class JSONObjectTest { + + @Test + void canSimpleSerializeAnObject() { + var test = new TestMe(true, "Wakk Wakka", 42, new TestMe()); + var o = new JSONObject(test); + assertEquals(true, o.optBoolean("aBoolean")); + assertEquals("Wakk Wakka", o.optString("aSillyString")); + assertEquals(42, o.optNumber("aNumber")); + assertNotNull(o.optJSONObject("aSub")); + } + + @Test + void isEmpty() { + var o = new JSONObject(); + assertTrue(o.isEmpty()); + o.put("foo", "bar"); + assertFalse(o.isEmpty()); + } + + @Test + void isNull() { + var o = new JSONObject(); + assertTrue(o.isNull("foo")); + o.put("foo", (Object)null); + assertTrue(o.isNull("foo")); + o.put("foo", 42); + assertFalse(o.isNull("foo")); + } + + @Test + void contructInvalid() { + JSONException ex = assertThrows(JSONException.class, () -> new JSONObject("foo")); + assertEquals("Invalid JSON", ex.getMessage()); + } + + @Test + void simpleConvert() { + String str = "{\"foo\": {\"baz\": 42}}"; + + JSONObject obj = new JSONObject(str); + + assertTrue(obj.has("foo")); + assertEquals(1, obj.length()); + assertEquals(42, obj.getJSONObject("foo").getInt("baz")); + } + + @Test + void doubles() { + JSONObject obj = new JSONObject(); + obj.put("key", 33.5d); + obj.put("not", "nan"); + + assertEquals(33.5d, obj.getDouble("key"), 4); + assertNotFound(() -> obj.getDouble("boo")); + assertJSONEx(() -> obj.getDouble("not"), "JSONObject[\"not\"] is not a number."); + isTypeAndValue(33.5, Double.class, obj.get("key")); + + assertEquals(33.5d, obj.optDouble("key"), 4); + assertEquals(66.6d, obj.optDouble("boo", 66.6d), 4); + assertEquals(Double.NaN, obj.optDouble("boo"), 4); + } + + @Test + void nullsAreSerialized() { + JSONObject obj = new JSONObject("{\"key1\": \"value\", \"key2\": null}"); + + assertEquals("{\"key1\":\"value\",\"key2\":null}", obj.toString()); + } + + @Test + void issue_366() { + JSONObject jsonObject = new JSONObject("{\"status\":\"OK\",\"message\":\"hive_1597818501335\"}"); + assertEquals("{\"status\":\"OK\",\"message\":\"hive_1597818501335\"}", jsonObject.toString()); + } + + @Test + void nullsAreSerializedOnPretty() { + JSONObject obj = new JSONObject("{\"key1\": \"value\", \"key2\": null}"); + + assertEquals("{\n" + + " \"key1\" : \"value\",\n" + + " \"key2\" : null\n" + + "}", obj.toString(3)); + } + + @Test + void floats() { + JSONObject obj = new JSONObject(); + obj.put("key", 33.5f); + obj.put("not", "nan"); + + assertEquals(33.5f, obj.getFloat("key"), 4); + assertNotFound(() -> obj.getFloat("boo")); + assertJSONEx(() -> obj.getFloat("not"), "JSONObject[\"not\"] is not a number."); + isTypeAndValue(33.5, Double.class, obj.get("key")); + + assertEquals(33.5f, obj.optFloat("key"), 4); + assertEquals(66.6f, obj.optFloat("boo", 66.6f), 4); + assertEquals(Float.NaN, obj.optFloat("boo"), 4); + } + + @Test + void longs() { + JSONObject obj = new JSONObject(); + obj.put("key", Long.MAX_VALUE); + obj.put("not", "nan"); + + assertEquals(Long.MAX_VALUE, obj.getLong("key")); + assertNotFound(() -> obj.getLong("boo")); + assertJSONEx(() -> obj.getLong("not"), "JSONObject[\"not\"] is not a number."); + isTypeAndValue(Long.MAX_VALUE, Long.class, obj.get("key")); + + assertEquals(Long.MAX_VALUE, obj.optLong("key")); + assertEquals(66L, obj.optLong("boo", 66)); + assertEquals(0L, obj.optLong("boo")); + } + + @Test + void booleans() { + JSONObject obj = new JSONObject(); + obj.put("key", true); + obj.put("not", "nan"); + + assertTrue(obj.getBoolean("key")); + assertNotFound(() -> obj.getBoolean("boo")); + assertJSONEx(() -> obj.getBoolean("not"), "JSONObject[\"not\"] is not a boolean."); + isTypeAndValue(true, Boolean.class, obj.get("key")); + + assertTrue(obj.optBoolean("key")); + assertTrue(obj.optBoolean("boo", true)); + assertFalse(obj.optBoolean("boo")); + } + + @Test + void ints() { + JSONObject obj = new JSONObject(); + obj.put("key", 33); + obj.put("not", "nan"); + + assertEquals(33, obj.getInt("key")); + assertNotFound(() -> obj.getInt("boo")); + assertJSONEx(() -> obj.getInt("not"), "JSONObject[\"not\"] is not a number."); + isTypeAndValue(33, Integer.class, obj.get("key")); + + assertEquals(33, obj.optInt("key")); + assertEquals(66, obj.optInt("boo", 66)); + assertEquals(0, obj.optInt("boo")); + } + + @Test + void numbers() { + Number tt = 33; + JSONObject obj = new JSONObject(); + obj.put("key", tt); + obj.put("not", "nan"); + + assertEquals(tt, obj.getNumber("key")); + assertNotFound(() -> obj.getNumber("boo")); + assertJSONEx(() -> obj.getNumber("not"), "JSONObject[\"not\"] is not a number."); + isTypeAndValue(tt, Number.class, obj.getNumber("key")); + + assertEquals(tt, obj.optNumber("key")); + assertEquals(66, obj.optNumber("boo", 66)); + assertEquals(0, obj.optNumber("boo")); + } + + @Test + void bigInts() { + JSONObject obj = new JSONObject(); + obj.put("key", BigInteger.valueOf(33)); + obj.put("not", "nan"); + + assertEquals(BigInteger.valueOf(33), obj.getBigInteger("key")); + assertNotFound(() -> obj.getBigInteger("boo")); + assertJSONEx(() -> obj.getBigInteger("not"), "JSONObject[\"not\"] is not a number."); + assertEquals(BigInteger.valueOf(33), obj.optBigInteger("key", BigInteger.TEN)); + assertEquals(BigInteger.TEN, obj.optBigInteger("boo", BigInteger.TEN)); + isTypeAndValue(33, Integer.class, obj.get("key")); + } + + @Test + void bigDecimal() { + BigDecimal value = BigDecimal.valueOf(33.5); + JSONObject obj = new JSONObject(); + obj.put("key", value); + obj.put("not", "nan"); + + assertEquals(value, obj.getBigDecimal("key")); + assertNotFound(() -> obj.getBigDecimal("boo")); + assertJSONEx(() -> obj.getBigDecimal("not"), "JSONObject[\"not\"] is not a number."); + assertEquals(value, obj.optBigDecimal("key", BigDecimal.TEN)); + assertEquals(BigDecimal.TEN, obj.optBigDecimal("boo", BigDecimal.TEN)); + isTypeAndValue(33.5, Double.class, obj.get("key")); + } + + @Test + void strings() { + JSONObject obj = new JSONObject(); + obj.put("key", "cheese"); + obj.put("not", 45); + + assertEquals("cheese", obj.getString("key")); + assertNotFound(() -> obj.getString("boo")); + assertEquals("45", obj.getString("not")); + assertEquals("cheese", obj.optString("key")); + assertEquals("logs", obj.optString("boo", "logs")); + assertEquals("", obj.optString("boo")); + isTypeAndValue("cheese", String.class, obj.get("key")); + } + + @Test + void jsonObjects() throws Exception { + JSONObject subObj = new JSONObject("{\"derp\": 42}"); + JSONObject obj = new JSONObject(); + obj.put("key", subObj); + obj.put("not", 45); + + assertEqualJson(subObj, obj.getJSONObject("key")); + assertNotFound(() -> obj.getJSONObject("boo")); + assertJSONEx(() -> obj.getJSONObject("not"), "JSONObject[\"not\"] is not a JSONObject."); + assertEqualJson(subObj, obj.optJSONObject("key")); + assertNull(obj.optJSONObject("boo")); + assertTrue(subObj.similar(obj.get("key"))); + } + + @Test + void jsonArrays() throws Exception { + var subObj = new JSONArray("[42]"); + JSONObject obj = new JSONObject(); + obj.put("key", subObj); + obj.put("not", 45); + + assertEqualJson(subObj, obj.getJSONArray("key")); + assertNotFound(() -> obj.getJSONArray("boo")); + assertJSONEx(() -> obj.getJSONArray("not"), "JSONObject[\"not\"] is not a JSONArray."); + assertEqualJson(subObj, obj.optJSONArray("key")); + assertNull(obj.optJSONArray("boo")); + assertTrue(subObj.similar(obj.get("key"))); + } + + @Test + void enums() { + JSONObject obj = new JSONObject(); + obj.put("key", fruit.orange); + obj.put("not", "nan"); + + assertEquals(fruit.orange, obj.getEnum(fruit.class, "key")); + assertJSONEx(() -> obj.getEnum(fruit.class, "not"), "JSONObject[\"not\"] is not an enum of type \"fruit\"."); + assertEquals(fruit.orange, obj.optEnum(fruit.class, "key")); + assertEquals(fruit.apple, obj.optEnum(fruit.class, "boo", fruit.apple)); + assertNull(obj.optEnum(fruit.class, "boo")); + isTypeAndValue(obj.get("key"), String.class, "orange"); + } + + @Test + void toStringIt() { + String str = "{\"foo\": 42}"; + + JSONObject obj = new JSONObject(str); + + assertEquals("{\"foo\":42}", obj.toString()); + } + + @Test + void toStringItIndent() { + String str = "{\"foo\": 42, \"bar\": true}"; + + JSONObject obj = new JSONObject(str); + + assertEquals("{\n" + + " \"foo\" : 42,\n" + + " \"bar\" : true\n" + + "}", obj.toString(3)); + } + + @Test + void objProperties() { + String str = "{\"foos\": [6,7,8]}"; + + JSONObject obj = new JSONObject(str); + + assertEquals(7, obj.getJSONArray("foos").get(1)); + assertEquals(7, obj.optJSONArray("foos").get(1)); + assertNull(obj.optJSONArray("bars")); + } + + @Test + void writer() { + String str = "{\"foo\":42}"; + + JSONObject obj = new JSONObject(str); + + StringWriter sw = new StringWriter(); + + obj.write(sw); + + assertEquals(str, sw.toString()); + } + + @Test + void writerIndent() { + String str = "{\"foo\": 42, \"bar\": true}"; + + JSONObject obj = new JSONObject(str); + + StringWriter sw = new StringWriter(); + + obj.write(sw, 3, 3); + + assertEquals("{\n" + + " \"foo\" : 42,\n" + + " \"bar\" : true\n" + + "}", sw.toString()); + } + + @Test + void remove() { + JSONObject obj = new JSONObject("{\"foo\": 42, \"bar\": true}"); + assertEquals(42, obj.remove("foo")); + assertNull(obj.remove("nothing")); + assertEquals("{\"bar\":true}", obj.toString()); + } + + @Test + void removeAThingThatDoesntExist() { + JSONObject obj = new JSONObject(); + obj.remove("foo"); + + assertEquals(0, obj.length()); + } + + @Test + void putReplace() { + JSONObject obj = new JSONObject("{\"bar\": 42}"); + assertEquals(42, obj.get("bar")); + assertSame(obj, obj.put("bar", 33)); + assertEquals(33, obj.get("bar")); + NullPointerException ex = assertThrows(NullPointerException.class, () -> obj.put(null, "hi")); + assertEquals("key == null", ex.getMessage()); + } + + @Test + void accumulateDoesNotCreate() { + JSONObject obj = new JSONObject(); + assertSame(obj, obj.accumulate("bar", 42)); + assertEquals(0, obj.length()); + } + + @Test + void accumulate() { + JSONObject obj = new JSONObject("{\"bar\": 42}"); + assertSame(obj, obj.accumulate("bar", 33)); + assertEquals(2, obj.getJSONArray("bar").length()); + assertEquals(42, obj.getJSONArray("bar").get(0)); + assertEquals(33, obj.getJSONArray("bar").get(1)); + } + + @Test + void accumulateNullKey() { + NullPointerException ex = assertThrows(NullPointerException.class, () -> new JSONObject().accumulate(null, "hi")); + assertEquals("Null key.", ex.getMessage()); + } + + @Test + void append() { + JSONObject obj = new JSONObject(); + assertSame(obj, obj.append("bar", 42)); + obj.append("bar", 33); + assertEquals(2, obj.getJSONArray("bar").length()); + assertEquals(42, obj.getJSONArray("bar").get(0)); + assertEquals(33, obj.getJSONArray("bar").get(1)); + } + + @Test + void appendNullKey() { + NullPointerException ex = assertThrows(NullPointerException.class, () -> new JSONObject().append(null, "hi")); + assertEquals("Null key.", ex.getMessage()); + } + + @Test + void appendToNotAnArrary() { + JSONObject obj = new JSONObject(); + assertSame(obj, obj.put("bar", "not")); + JSONException ex = assertThrows(JSONException.class, () -> obj.append("bar", 33)); + assertEquals("JSONObject[\"bar\"] is not a JSONArray.", ex.getMessage()); + } + + @Test + void increment() { + JSONObject obj = new JSONObject(); + assertSame(obj, obj.increment("cool-beans")); + assertEquals(1, obj.get("cool-beans")); + obj.increment("cool-beans") + .increment("cool-beans") + .increment("cool-beans"); + assertEquals(4, obj.get("cool-beans")); + } + + @Test + void incrementDouble() { + JSONObject obj = new JSONObject(); + assertSame(obj, obj.put("cool-beans", 1.5)); + obj.increment("cool-beans"); + assertEquals(2.5, obj.get("cool-beans")); + } + + + @Test + void putOnce() { + JSONObject obj = new JSONObject(); + assertSame(obj, obj.putOnce("foo", "bar")); + assertJSONEx(() -> obj.putOnce("foo", "baz"), "Duplicate key \"foo\""); + assertEquals("bar", obj.getString("foo")); + } + + @Test + void optPut() { + JSONObject obj = new JSONObject(); + assertSame(obj, obj.putOpt("foo", "bar")); + obj.putOpt(null, "bar"); + obj.putOpt("foo", null); + assertEquals("bar", obj.get("foo")); + obj.putOpt("foo", "qux"); + assertEquals("qux", obj.get("foo")); + } + + @Test + void keySet() { + JSONObject obj = new JSONObject(); + obj.put("one", "a"); + obj.put("two", "b"); + Set exp = newHashSet("one", "two"); + assertEquals(exp, obj.keySet()); + assertEquals(exp, newHashSet(obj.keys())); + } + + @Test + void similar() { + JSONObject obj1 = new JSONObject("{\"foo\":42}"); + JSONObject obj2 = new JSONObject("{\"foo\":42}"); + assertTrue(obj1.similar(obj2)); + obj1.put("foo", -9); + assertFalse(obj1.similar(obj2)); + } + + @Test + void query() { + JSONObject obj = new JSONObject("{\"a\":{\"b\": 42}}"); + assertEquals(42, obj.query("/a/b")); + } + + @Test + void maps() { + JSONObject obj = new JSONObject("{\"foo\": {\"bar\": 42}, \"qux\": 21474836475, \"baz\": 55}"); + + Map map = obj.toMap(); + assertEquals(55, map.get("baz")); + assertEquals(21474836475L, map.get("qux")); + JSONObject sub = (JSONObject) obj.get("foo"); + assertEquals(42, sub.get("bar")); + } + + @Test + void names() { + JSONObject obj = new JSONObject(of("foo", 1, "bar", 2, "baz", 3)); + var names = obj.names(); + assertEquals( + newHashSet("foo", "bar", "baz"), + newHashSet(names.toList()) + ); + } + + @Test + void toJSONArray() { + var o = new JSONObject(of("foo","bar","baz",42)); + + assertNull(o.toJSONArray(new JSONArray())); + + assertEquals(new JSONArray(asList("bar", 42)), + o.toJSONArray(new JSONArray(asList("foo", "baz")))); + + assertEquals(new JSONArray(asList(null, null)), + new JSONObject().toJSONArray(new JSONArray(asList("foo", "baz")))); + } + + @Test + void putCollection() { + var o = new JSONObject(); + o.put("foo", asList(1,2,3)); + assertEquals("{\"foo\":[1,2,3]}", o.toString()); + } + + @Test + void putObjectAsMap() { + var o = new JSONObject(); + o.put("foo", of("baz", 42)); + assertEquals("{\"foo\":{\"baz\":42}}", o.toString()); + } + + @Test + @SuppressWarnings("ConstantConditions") + void stringToValue() { + assertSame(JSONObject.NULL, JSONObject.stringToValue("null")); + assertEquals(true, JSONObject.stringToValue("true")); + assertEquals(false, JSONObject.stringToValue("false")); + assertEquals(42, JSONObject.stringToValue("42")); + assertEquals(45.25, JSONObject.stringToValue("45.25")); + assertEquals(-45.25, JSONObject.stringToValue("-45.25")); + NullPointerException ex = assertThrows(NullPointerException.class, () -> JSONObject.stringToValue(null)); + } + + @Test + void quote() { + assertEquals("\"\\\"foo\\\"hoo\"", JSONObject.quote("\"foo\"hoo")); + } + + @Test + void quoteWriter() throws IOException { + StringWriter w = new StringWriter(); + Writer quote = JSONObject.quote("\"foo\"hoo", w); + assertEquals("\"\\\"foo\\\"hoo\"", quote.toString()); + } + + @Test + void wrapPrimitives() { + assertEquals(42, JSONObject.wrap(42)); + assertEquals(42.5, JSONObject.wrap(42.5)); + assertSame(JSONObject.NULL, JSONObject.wrap(null)); + assertEquals(true, JSONObject.wrap(true)); + } + + @Test + void wrapObjects() { + assertTrue(new JSONArray(asList(1,2,3)).similar(JSONObject.wrap(asList(1,2,3)))); + assertTrue(new JSONArray(asList(1,2,3)).similar(JSONObject.wrap(new int[]{1,2,3}))); + assertTrue(new JSONObject(of("f",1)).similar(JSONObject.wrap(of("f",1)))); + assertTrue(new JSONObject().similar(JSONObject.wrap(new Foo("hi")))); + } + + @Test + void doubleToString() { + assertEquals("42", JSONObject.doubleToString(42)); + assertEquals("42.5643", JSONObject.doubleToString(42.5643)); + } + + @Test + void numberToString() { + assertEquals("42", JSONObject.numberToString(42)); + assertEquals("42.5643", JSONObject.numberToString(42.5643f)); + } + + @Test + void valueToString() { + assertEquals("null", JSONObject.valueToString(null)); + assertEquals("42", JSONObject.valueToString(42)); + assertEquals("42.5643", JSONObject.valueToString(42.5643f)); + assertEquals("\"Hello World\"", JSONObject.valueToString("Hello World")); + assertEquals("{\"bar\":\"me\"}", JSONObject.valueToString(new Foo("me"))); + assertEquals("{}", JSONObject.valueToString(new JSONObject())); + assertEquals("[]", JSONObject.valueToString(new JSONArray())); + } + + @Test + void getNames() { + assertArrayEquals(null, JSONObject.getNames(new JSONObject())); + assertArrayEquals(new String[]{"a","b"}, JSONObject.getNames(new JSONObject(of("a",1,"b",2)))); + } + + private void assertNotFound(Executable exRunnable) { + assertJSONEx(exRunnable, "JSONObject[\"boo\"] not found."); + } + + public static void assertJSONEx(Executable exRunnable, String message) { + JSONException ex = assertThrows(JSONException.class, exRunnable); + assertEquals(message, ex.getMessage()); + } + + public static void assertEqualJson(Object subObj, Object value){ + try { + JSONAssert.assertEquals(subObj.toString(), value.toString(), true); + } catch (org.json.JSONException e) { + throw new UnirestException(e); + } + } + + public static void isTypeAndValue(Object o, Class type, Object value) { + assertEquals(o, value); + assertTrue(type.isInstance(o)); + } + + public enum fruit {orange, apple} + + public static class TestMe { + private boolean aBoolean; + private String aSillyString; + private Integer aNumber; + private TestMe aSub; + + public TestMe(){ } + + public TestMe(boolean bool, String aString, int aNumber, TestMe aSub) { + this.aBoolean = bool; + this.aSillyString = aString; + this.aNumber = aNumber; + this.aSub = aSub; + } + + public boolean isaBoolean() { + return aBoolean; + } + + public void setaBoolean(boolean aBoolean) { + this.aBoolean = aBoolean; + } + + public String getaSillyString() { + return aSillyString; + } + + public void setaSillyString(String aSillyString) { + this.aSillyString = aSillyString; + } + + public Integer getaNumber() { + return aNumber; + } + + public void setaNumber(Integer aNumber) { + this.aNumber = aNumber; + } + + public TestMe getaSub() { + return aSub; + } + + public void setaSub(TestMe aSub) { + this.aSub = aSub; + } + } +} diff --git a/unirest-modules-jackson/src/test/java/kong/unirest/modules/jackson/JSONPointerTest.java b/unirest-modules-jackson/src/test/java/kong/unirest/modules/jackson/JSONPointerTest.java new file mode 100644 index 000000000..170716bc7 --- /dev/null +++ b/unirest-modules-jackson/src/test/java/kong/unirest/modules/jackson/JSONPointerTest.java @@ -0,0 +1,187 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.modules.jackson; + +import com.google.common.base.Charsets; +import com.google.common.io.Resources; +import kong.unirest.core.json.JSONObject; +import kong.unirest.core.json.JSONPointer; +import kong.unirest.core.json.JSONPointerException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +import static org.junit.jupiter.api.Assertions.*; + +class JSONPointerTest { + // https://tools.ietf.org/html/rfc6901 + private static final String RFC_TEST = getResource("JSON_POINTER_REF.json"); + private final JSONObject obj = new JSONObject(RFC_TEST); + + public static String getResource(String resourceName){ + try { + return Resources.toString(Resources.getResource(resourceName), Charsets.UTF_8); + }catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Test + void nullQuery() { + var ex = assertThrows(NullPointerException.class, () -> obj.query((String)null)); + assertEquals("pointer cannot be null", ex.getMessage()); + } + + @Test + void invalidPathQuery() { + var ex = assertThrows(IllegalArgumentException.class, () -> obj.query("foo")); + assertEquals("a JSON pointer should start with '/' or '#/'", ex.getMessage()); + } + + @Test + void invalidPathQuery_downpath() { + var ex = assertThrows(JSONPointerException.class, () -> obj.query("/shwoop/dedoop")); + assertEquals("Path Segment Missing: shwoop", ex.getMessage()); + } + + @Test + void arrayPartThatDoesNotExist() { + var ex = assertThrows(JSONPointerException.class, () -> obj.query("/foo/5")); + assertEquals("index 5 is out of bounds - the array has 2 elements", ex.getMessage()); + } + + @Test + void referenceAnArrayAsAThing() { + var ex = assertThrows(JSONPointerException.class, () -> obj.query("/foo/bar")); + assertEquals("bar is not an array index", ex.getMessage()); + } + + @Test + @SuppressWarnings("RedundantCast") + void constructorMayNotTakeNull() { + var ex = assertThrows(NullPointerException.class, () -> new JSONPointer((String) null)); + assertEquals("pointer cannot be null", ex.getMessage()); + } + + @Test + void toStringReturnsOriginalString() { + assertEquals("/foo/g~0h/baz", new JSONPointer("/foo/g~h/baz").toString()); + assertEquals("/foo/g~0h/baz", JSONPointer.compile("/foo/g~h/baz").toString()); + } + + @Test + void canGetAsURIFragmanet() { + assertEquals("#/foo/g%7Eh/baz", new JSONPointer("/foo/g~h/baz").toURIFragment()); + } + + @Test + void elementInObjectDoesNotExist() { + assertNull(obj.query("/derpa")); + } + + @Test + void testRef_all() throws Exception { + assertQueryJson(RFC_TEST, ""); + } + + @Test + void testRef_Array() throws Exception { + assertQueryJson("[\"bar\", \"baz\"]", "/foo"); + } + + @Test + void testRef_ArrayZero() { + assertEquals("bar", obj.query("/foo/0").toString()); + } + + @Test + void testRef_Slash() { + assertEquals(0, obj.query("/")); + } + + @Test + void testRef_ab() { + assertEquals(1, obj.query("/a~1b")); + } + + @Test + void testRef_cd() { + assertEquals(2, obj.query("/c%d")); + } + + @Test + void testRef_ef() { + assertEquals(3, obj.query("/e^f")); + } + + @Test + void testRef_gh() { + assertEquals(4, obj.query("/g|h")); + } + + @Test + void testRef_ij() { + assertEquals(5, obj.query("/i\\j")); + } + + @Test + void testRef_kl() { + assertEquals(6, obj.query("/k\"l")); + } + + @Test + void testRef_space() { + assertEquals(7, obj.query("/ ")); + } + + @Test + void testRef_mn() { + assertEquals(8, obj.query("/m~0n")); + } + + @Test + void letsGoDeep() { + assertEquals(true, obj.query("/cucu/0/banana/pants")); + } + + @Test + void builder(){ + JSONPointer pointer = JSONPointer + .builder() + .append("foo") + .append(4) + .append("n~t") + .append("bar/1") + .build(); + + assertEquals(new JSONPointer("/foo/4/n~0t/bar/1").toString(), + pointer.toString()); + } + + private void assertQueryJson(String s, String s2) throws Exception { + JSONAssert.assertEquals(s, obj.query(s2).toString(), true); + } + +} diff --git a/unirest-modules-jackson/src/test/java/kong/unirest/modules/jackson/JacksonCoreFactoryTest.java b/unirest-modules-jackson/src/test/java/kong/unirest/modules/jackson/JacksonCoreFactoryTest.java new file mode 100644 index 000000000..f65aab12f --- /dev/null +++ b/unirest-modules-jackson/src/test/java/kong/unirest/modules/jackson/JacksonCoreFactoryTest.java @@ -0,0 +1,51 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.modules.jackson; + +import kong.unirest.core.json.CoreFactory; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class JacksonCoreFactoryTest { + @Test + void canLoadByServiceLocator() { + assertThat(CoreFactory.findEngineWithServiceLocator()) + .isInstanceOf(JacksonEngine.class); + } + + @Test + void canLoadByKnownClassNames() { + assertThat(CoreFactory.findEngineWithClassLoader()) + .isInstanceOf(JacksonEngine.class); + } + + @Test + void willLoadOneWaOrAnother() { + assertThat(CoreFactory.findEngine()) + .isInstanceOf(JacksonEngine.class); + } +} diff --git a/unirest-modules-jackson/src/test/java/kong/unirest/modules/jackson/JacksonObjectMapperTest.java b/unirest-modules-jackson/src/test/java/kong/unirest/modules/jackson/JacksonObjectMapperTest.java new file mode 100644 index 000000000..cc1949023 --- /dev/null +++ b/unirest-modules-jackson/src/test/java/kong/unirest/modules/jackson/JacksonObjectMapperTest.java @@ -0,0 +1,134 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* +The MIT License + +Copyright for portions of unirest-java are held by Kong Inc (c) 2018. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +package kong.unirest.modules.jackson; + +import tools.jackson.databind.DeserializationFeature; +import kong.unirest.core.GenericType; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; +import tools.jackson.databind.json.JsonMapper; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class JacksonObjectMapperTest { + private JacksonObjectMapper om = new JacksonObjectMapper(); + + @Test + void canWrite() throws JSONException { + var test = new TestMe("foo", 42, new TestMe("bar", 666, null)); + + String json = om.writeValue(test); + + JSONAssert.assertEquals( + "{\"text\":\"foo\",\"nmbr\":42,\"another\":{\"text\":\"bar\",\"nmbr\":666,\"another\":null}}" + , json + , true + ); + } + + @Test + void canRead(){ + var test = om.readValue("{\"text\":\"foo\",\"nmbr\":42,\"another\":{\"text\":\"bar\",\"nmbr\":666,\"another\":null}}", + TestMe.class); + + assertEquals("foo", test.text); + assertEquals(42, test.nmbr); + assertEquals("bar", test.another.text); + assertEquals(666, test.another.nmbr); + assertNull(test.another.another); + } + + @Test + void canReadGenerics(){ + var testList = om.readValue("[{\"text\":\"foo\",\"nmbr\":42,\"another\":{\"text\":\"bar\",\"nmbr\":666,\"another\":null}}]", + new GenericType>(){}); + + var test = testList.get(0); + + assertEquals("foo", test.text); + assertEquals(42, test.nmbr); + assertEquals("bar", test.another.text); + assertEquals(666, test.another.nmbr); + assertNull(test.another.another); + } + + @Test + void configSoItFails() { + final JsonMapper jom = JsonMapper.builder() + .enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .build(); + JacksonObjectMapper j = new JacksonObjectMapper(jom); + + try { + j.readValue("{\"something\": [1,2,3] }", TestMe.class); + fail("Should have thrown"); + } catch (Exception e) { + assertEquals("tools.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized property \"something\" (class kong.unirest.modules.jackson.JacksonObjectMapperTest$TestMe), not marked as ignorable (3 known properties: \"another\", \"nmbr\", \"text\")\n" + + " at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); byte offset: #UNKNOWN] (through reference chain: kong.unirest.modules.jackson.JacksonObjectMapperTest$TestMe[\"something\"])", e.getMessage()); + } + } + + public static class TestMe { + public String text; + public int nmbr; + public TestMe another; + + public TestMe(){} + + public TestMe(String text, Integer nmbr, TestMe another) { + this.text = text; + this.nmbr = nmbr; + this.another = another; + } + } +} diff --git a/unirest-modules-jackson/src/test/java/kong/unirest/modules/jackson/JacksonTimeTests.java b/unirest-modules-jackson/src/test/java/kong/unirest/modules/jackson/JacksonTimeTests.java new file mode 100644 index 000000000..83c16d820 --- /dev/null +++ b/unirest-modules-jackson/src/test/java/kong/unirest/modules/jackson/JacksonTimeTests.java @@ -0,0 +1,332 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.modules.jackson; + +import com.fasterxml.jackson.annotation.JsonInclude; +import kong.unirest.core.json.JSONObject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.time.*; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +import static org.junit.jupiter.api.Assertions.*; + +class JacksonTimeTests { + + JacksonObjectMapper om = new JacksonObjectMapper(); + + @BeforeEach + void before() { + TimeZone.setDefault(TimeZone.getTimeZone("UTC")); + } + + @Test + void serializeEverythingNull() { + String actual = om.writeValue(new TestDates()); + assertEquals("{}", actual); + } + + @Test + void serializeDate_iso_with_time() { + var date = getDate("1985-07-03T18:00:00.042Z"); + + String actual = om.writeValue(date); + + assertEquals("{\"date\":489261600042}", actual); + } + + @Test + void serializeDate_iso_no_time() { + var date = getDate(new Date(489196800000L)); + + String actual = om.writeValue(date); + + assertEquals("{\"date\":489196800000}", actual); + } + + @Test + void deserializeDate_null() { + var back = getTestDate("date", null); + + assertNull(back.getDate()); + } + + @Test + void deserializeDate_iso_with_time() { + var back = getTestDate("date", "1985-07-03T18:30:00.042Z"); + + assertEquals(489263400042L, back.getDate().getTime()); + } + + @Test + void deserializeDate_iso_with_NoTime() { + var back = getTestDate("date", "1985-07-03"); + + assertEquals(489196800000L, back.getDate().getTime()); + } + + @Test + void deserializeDate_iso_datetime_noMillies() { + var back = getTestDate("date", "1985-07-03T18:30:00Z"); + + assertEquals(489263400000L, back.getDate().getTime()); + } + + @Test + void deserializeDate_iso_datetime_noSeconds() { + var back = getTestDate("date", "1985-07-03T18:30Z"); + + assertEquals(489263400000L, back.getDate().getTime()); + } + + @Test + void getDatesFromNumbers() { + var back = getTestDate("date", 42); + + assertEquals(new Date(42), back.getDate()); + } + + @Test + void canSerializeCalendar() { + var test = getCalendar("1985-07-03T18:00:00.042Z"); + + String actual = om.writeValue(test); + assertEquals("{\"calendar\":489261600042}", actual); + } + + @Test + @SuppressWarnings("MagicConstant") + void canSerializeCalendar_no_time() { + var cal = GregorianCalendar.getInstance(); + cal.set(1985, 6, 3, 0, 0, 0); + cal.set(Calendar.MILLISECOND, 0); + var test = getCalendar(cal); + + String actual = om.writeValue(test); + assertEquals("{\"calendar\":489196800000}", actual); + } + + @Test + void deserializeCalendar_iso_unixDates() { + var back = getTestDate("calendar", 489263400042L); + + assertEquals(489263400042L, back.getCalendar().getTimeInMillis()); + } + + @Test + void deserializeCalendar_iso_datetime() { + var back = getTestDate("calendar", "1985-07-03T18:30:00.042Z"); + + assertEquals(489263400042L, back.getCalendar().getTimeInMillis()); + } + + @Test + void deserializeCalendar_iso_datetime_noMillies() { + var back = getTestDate("calendar", "1985-07-03T18:30:00Z"); + + assertEquals(489263400000L, back.getCalendar().getTimeInMillis()); + } + + @Test + void deserializeCalendar_iso_datetime_noSeconds() { + var back = getTestDate("calendar", "1985-07-03T18:30Z"); + + assertEquals(489263400000L, back.getCalendar().getTimeInMillis()); + } + + @Test + void deserializeCalendar_iso_date() { + var back = getTestDate("calendar", "1985-07-03"); + + assertEquals(489196800000L, back.getCalendar().getTimeInMillis()); + } + + @Test + void canSerializeZonedDateTimes() { + var test = new TestDates(); + test.setZonedDateTime(ZonedDateTime.parse("1985-07-03T18:00:00.042Z").withFixedOffsetZone()); + + String actual = om.writeValue(test); + assertEquals("{\"zonedDateTime\":489261600.042000000}", actual); + } + + @Test + void deserializeZonedDateTime_iso_datetime() { + var back = getTestDate("zonedDateTime", "1985-07-03T18:30:00.042Z"); + var parse = ZonedDateTime.parse("1985-07-03T18:30:00.042Z"); + + assertEquals(parse, back.getZonedDateTime()); + } + + @Test + void deserializeZonedDateTime_iso_with_offset() { + var back = getTestDate("zonedDateTime", "1985-07-03T18:30:00.042+02:00"); + var parse = ZonedDateTime.parse("1985-07-03T16:30:00.042Z"); + + assertEquals(parse, back.getZonedDateTime()); + } + + @Test + void canSerializeLocalDateTimes() { + var test = new TestDates(); + test.setLocalDateTime(LocalDateTime.parse("1985-07-03T18:00:00.042")); + + String actual = om.writeValue(test); + assertEquals("{\"localDateTime\":[1985,7,3,18,0,0,42000000]}", actual); + } + + @Test + void deserializeLocalDateTime_iso_datetime() { + var back = getTestDate("localDateTime", "1985-07-03T18:00:00.042"); + + assertEquals(LocalDateTime.parse("1985-07-03T18:00:00.042"), back.getLocalDateTime()); + } + + @Test + void canSerializeLocalDate() { + var test = new TestDates(); + test.setLocalDate(LocalDate.parse("1985-07-03")); + + String actual = om.writeValue(test); + assertEquals("{\"localDate\":[1985,7,3]}", actual); + } + + @Test + void deserializeLocalDate_iso_datetime() { + var back = getTestDate("localDate", "1985-07-03T18:00:00.042"); + + assertEquals(LocalDate.parse("1985-07-03"), back.getLocalDate()); + } + + @Test + void deserializeLocalDate_iso_datetime_noTime() { + var back = getTestDate("localDate", "1985-07-03"); + + assertEquals(LocalDate.parse("1985-07-03"), back.getLocalDate()); + } + + @Test + void doNotEscapeHTML() { + var s = new TestString(); + s.test = "it's a && b || c + 1!?"; + + String res = om.writeValue(s); + + assertEquals("{\"test\":\"it's a && b || c + 1!?\"}", res); + } + + public static class TestString { + public String test; + } + + private TestDates getTestDate(String key, Object date) { + return om.readValue(getJson(key, date), TestDates.class); + } + + private String getJson(String key, Object value){ + JSONObject object = new JSONObject(); + object.put(key, value); + return object.toString(); + } + + @SuppressWarnings("SameParameterValue") + private TestDates getDate(String date) { + Date from = Date.from(ZonedDateTime.parse(date).withFixedOffsetZone().toInstant()); + return getDate(from); + } + + private TestDates getDate(Date from) { + var test = new TestDates(); + test.setDate(from); + return test; + } + + @SuppressWarnings("SameParameterValue") + private TestDates getCalendar(String date) { + Calendar from = GregorianCalendar.from(ZonedDateTime.parse(date)); + return getCalendar(from); + } + + private TestDates getCalendar(Calendar from) { + var test = new TestDates(); + test.setCalendar(from); + return test; + } + + @JsonInclude(JsonInclude.Include.NON_EMPTY) + public static class TestDates { + + private Date date; + private Calendar calendar; + private ZonedDateTime zonedDateTime; + private LocalDateTime localDateTime; + private LocalDate localDate; + + public Date getDate() { + return date; + } + + public void setDate(Date date) { + this.date = date; + } + + public Calendar getCalendar() { + return calendar; + } + + public void setCalendar(Calendar calendar) { + this.calendar = calendar; + } + + public ZonedDateTime getZonedDateTime() { + return zonedDateTime; + } + + public void setZonedDateTime(ZonedDateTime zonedDateTime) { + this.zonedDateTime = zonedDateTime; + } + + public LocalDateTime getLocalDateTime() { + return localDateTime; + } + + public void setLocalDateTime(LocalDateTime localDateTime) { + this.localDateTime = localDateTime; + } + + public LocalDate getLocalDate() { + return localDate; + } + + public void setLocalDate(LocalDate localDate) { + this.localDate = localDate; + } + } +} diff --git a/unirest-modules-jackson/src/test/resources/JSON_POINTER_REF.json b/unirest-modules-jackson/src/test/resources/JSON_POINTER_REF.json new file mode 100644 index 000000000..9fc119831 --- /dev/null +++ b/unirest-modules-jackson/src/test/resources/JSON_POINTER_REF.json @@ -0,0 +1,19 @@ +{ + "foo": ["bar", "baz"], + "": 0, + "a/b": 1, + "c%d": 2, + "e^f": 3, + "g|h": 4, + "i\\j": 5, + "k\"l": 6, + " ": 7, + "m~n": 8, + "cucu": [ + { + "banana": { + "pants" : true + } + } + ] +} \ No newline at end of file diff --git a/unirest-modules-mocks/pom.xml b/unirest-modules-mocks/pom.xml new file mode 100644 index 000000000..3cc334dc9 --- /dev/null +++ b/unirest-modules-mocks/pom.xml @@ -0,0 +1,32 @@ + + + + com.konghq + unirest-java-parent + 4.10.3-SNAPSHOT + + + 4.0.0 + + unirest-modules-mocks + unirest-modules-mocks + + + ${project.parent.basedir} + + + + + com.konghq + unirest-java-core + ${project.version} + provided + + + com.konghq + unirest-modules-gson + ${project.version} + test + + + \ No newline at end of file diff --git a/unirest-modules-mocks/src/main/java/kong/unirest/core/Assert.java b/unirest-modules-mocks/src/main/java/kong/unirest/core/Assert.java new file mode 100644 index 000000000..e9aa17b2f --- /dev/null +++ b/unirest-modules-mocks/src/main/java/kong/unirest/core/Assert.java @@ -0,0 +1,73 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +/** + * a set of assertable things about the requests that the mock client handled. + * Each instance of an Assert represents one or more calls to a specific method/path + */ +public interface Assert { + /** + * Assert that any request to this method/path contained this header + * @param key the expected header key + * @param value the expected header value + * @return this Assert instance + * @throws UnirestAssertion when header does not exist + */ + Assert hadHeader(String key, String value); + + /** + * Assert that any the request sent this body. this only applies to non-multipart requests. + * @param expected the expected body + * @return this Assert instance + * @throws UnirestAssertion when body does not exist + */ + Assert hadBody(String expected); + + /** + * Assert that any the request sent a multipart field + * @param name the field name + * @param value the field value + * @return this Assert instance + * @throws UnirestAssertion when body does not exist + */ + Assert hadField(String name, String value); + + /** + * assert that this instance of method/path was invoked x times + * @param i the number of times invoked. + * @return this Assert instance + * @throws UnirestAssertion when the invocation count is not x + */ + Assert wasInvokedTimes(int i); + + /** + * verify that all Expectations were fulfilled at least once. + * @return this Assert instance + * @throws UnirestAssertion when all expectations have not been fulfilled + */ + Assert verifyAll(); +} diff --git a/unirest-modules-mocks/src/main/java/kong/unirest/core/BodyMatcher.java b/unirest-modules-mocks/src/main/java/kong/unirest/core/BodyMatcher.java new file mode 100644 index 000000000..62e9c7e63 --- /dev/null +++ b/unirest-modules-mocks/src/main/java/kong/unirest/core/BodyMatcher.java @@ -0,0 +1,46 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.util.List; + +/** + * Body Matchers can be used to evaluate a entity body. + * This is represented by one or more (in the case of multi-part) sections + * these sections are represented as strings in the same way as toString represents the part + * For a single entity like a JSON payload this is simply going to be the JSON String + * For field params it will be a key value pair like 'fruit=orange` + * For binary types it will be some kind of indication like a file path. + */ +public interface BodyMatcher { + /** + * indicates if the Matcher succeeded in matching the body + * @param body the list of body parts + * @return MatchStatus indicating if the Matcher succeeded in matching the body + */ + MatchStatus matches(List body); + +} diff --git a/unirest-modules-mocks/src/main/java/kong/unirest/core/EqualsBodyMatcher.java b/unirest-modules-mocks/src/main/java/kong/unirest/core/EqualsBodyMatcher.java new file mode 100644 index 000000000..1b48feb5d --- /dev/null +++ b/unirest-modules-mocks/src/main/java/kong/unirest/core/EqualsBodyMatcher.java @@ -0,0 +1,79 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.util.List; +import java.util.Objects; + +/** + * A {@link BodyMatcher} implementation that performs exact string equality matching. + *

+ * This matcher compares the actual request body against an expected string value using + * {@link Objects#equals(Object, Object)}. It requires exactly one body element and + * performs a strict character-by-character comparison. + *

+ * + *

Usage Example:

+ *
{@code
+ * mockClient.expect(HttpMethod.POST, "/api/users")
+ *     .body(new EqualsBodyMatcher("{\"name\":\"John\"}"))
+ *     .thenReturn()
+ *     .withStatus(201);
+ * }
+ * + * @see BodyMatcher + * @see MatchStatus + */ +public class EqualsBodyMatcher implements BodyMatcher { + private final String expected; + + /** + * Creates a new EqualsBodyMatcher with the expected body content. + * + * @param body the expected body string to match against + */ + public EqualsBodyMatcher(String body) { + this.expected = body; + } + + /** + * {@inheritDoc} + *

+ * Matches if the body list contains exactly one element that equals the expected string. + * Uses {@link Objects#equals(Object, Object)} for null-safe comparison. + *

+ * + * @param body the list of body content strings from the actual request + * @return a {@link MatchStatus} indicating whether the body matched, + * with the expected value for error reporting + * @throws AssertionError if matching fails unexpectedly + */ + @Override + public MatchStatus matches(List body) throws AssertionError { + return new MatchStatus(body.size() == 1 && Objects.equals(expected, body.get(0)), expected); + } + +} diff --git a/unirest-modules-mocks/src/main/java/kong/unirest/core/Expectation.java b/unirest-modules-mocks/src/main/java/kong/unirest/core/Expectation.java new file mode 100644 index 000000000..aa33faa9f --- /dev/null +++ b/unirest-modules-mocks/src/main/java/kong/unirest/core/Expectation.java @@ -0,0 +1,199 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import kong.unirest.core.json.JSONElement; + +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * Represents an expectation for a particular HTTP method and path in the mock client. + *

+ * An Expectation defines the criteria that an incoming request must match, + * as well as the response that should be returned when a match occurs. + * Expectations are created via {@link MockClient#expect(HttpMethod, String)} and support + * a fluent API for configuring matching criteria and responses. + *

+ * + *

Usage Example:

+ *
{@code
+ * // Set up an expectation with request matching and response
+ * mockClient.expect(HttpMethod.POST, "/api/users")
+ *     .header("Content-Type", "application/json")
+ *     .queryString("active", "true")
+ *     .body("{\"name\":\"John\"}")
+ *     .thenReturn("{\"id\":1,\"name\":\"John\"}")
+ *     .withStatus(201);
+ *
+ * // Verify the expectation was met
+ * mockClient.expect(HttpMethod.POST, "/api/users")
+ *     .verify(Times.exactlyOnce());
+ * }
+ * + * @see MockClient + * @see ExpectedResponse + * @see Times + * @see BodyMatcher + */ +public interface Expectation { + + /** + * Adds an expected header that must be present in the request. + * + * @param key the header name + * @param value the expected header value + * @return this Expectation for method chaining + */ + Expectation header(String key, String value); + + /** + * Adds an expected query string parameter that must be present in the request URL. + * + * @param key the query parameter name + * @param value the expected query parameter value + * @return this Expectation for method chaining + */ + Expectation queryString(String key, String value); + + /** + * Sets the expected request body using exact string matching. + * + * @param body the expected body content + * @return this Expectation for method chaining + */ + Expectation body(String body); + + /** + * Sets a custom body matcher for flexible request body matching. + *

+ * Use this method when you need more sophisticated body matching logic, + * such as JSON comparison, regex matching, or partial content matching. + *

+ * + * @param matcher the {@link BodyMatcher} implementation to use + * @return this Expectation for method chaining + * @see EqualsBodyMatcher + */ + Expectation body(BodyMatcher matcher); + + /** + * Configures this expectation to return an empty response. + *

+ * Use the returned {@link ExpectedResponse} to further configure + * the response status and headers. + *

+ * + * @return an {@link ExpectedResponse} for configuring the response details + */ + ExpectedResponse thenReturn(); + + /** + * Configures this expectation to return a response with the specified string body. + * + * @param body the response body content + * @return an {@link ExpectedResponse} for configuring additional response details + */ + ExpectedResponse thenReturn(String body); + + /** + * Configures this expectation to return a response with a JSON body. + *

+ * The JSON element will be serialized to a string when the response is built. + *

+ * + * @param jsonObject the JSON element ({@link kong.unirest.core.json.JSONObject} or {@link kong.unirest.core.json.JSONArray}) + * @return an {@link ExpectedResponse} for configuring additional response details + */ + ExpectedResponse thenReturn(JSONElement jsonObject); + + /** + * Configures this expectation to return a response with a serialized POJO body. + *

+ * The object will be serialized using the configured object mapper. + *

+ * + * @param pojo the object to serialize as the response body + * @return an {@link ExpectedResponse} for configuring additional response details + */ + ExpectedResponse thenReturn(Object pojo); + + /** + * Configures this expectation with a dynamic response body supplier. + *

+ * The supplier is invoked at response time, allowing for dynamic response generation. + *

+ * + * @param supplier a supplier that provides the response body string + * @return an {@link ExpectedResponse} for configuring additional response details + */ + ExpectedResponse thenReturn(Supplier supplier); + + /** + * Configures this expectation with a function that generates the response based on the request. + *

+ * This allows for full control over response generation based on request details, + * making it useful for building more complex test doubles that implement service logic. + *

+ * + * @param fun a function that takes the incoming {@link HttpRequest} and returns an {@link ExpectedResponse} + */ + void thenReturn(Function, ExpectedResponse> fun); + + /** + * Verifies that this expectation was fulfilled at least once. + *

+ * This is equivalent to calling {@code verify(Times.atLeastOnce())}. + *

+ * + * @throws UnirestAssertion if the expectation was not fulfilled + */ + void verify(); + + /** + * Verifies that this expectation was fulfilled the specified number of times. + * + * @param times the expected invocation count + * @throws UnirestAssertion if the expectation was not fulfilled the expected number of times + * @see Times + */ + void verify(Times times); + + /** + * Sets the expected number of times this expectation should be invoked. + *

+ * This can be used during setup to define how many times a request is expected, + * and then verified later with {@link MockClient#verifyAll()}. + *

+ * + * @param times the expected invocation count + * @return this Expectation for method chaining + * @see Times#never() + * @see Times#exactlyOnce() + * @see Times#exactly(int) + */ + Expectation times(Times times); +} diff --git a/unirest-modules-mocks/src/main/java/kong/unirest/core/ExpectedResponse.java b/unirest-modules-mocks/src/main/java/kong/unirest/core/ExpectedResponse.java new file mode 100644 index 000000000..1366a99a3 --- /dev/null +++ b/unirest-modules-mocks/src/main/java/kong/unirest/core/ExpectedResponse.java @@ -0,0 +1,172 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import kong.unirest.core.json.JSONElement; + +import java.util.function.Supplier; + +/** + * Represents the expected response configuration for a mock HTTP request. + *

+ * This interface provides a fluent API for configuring mock responses including + * HTTP status codes, headers, and response bodies. It is typically obtained through + * {@link Expectation#thenReturn()} or {@link MockClient#defaultResponse()}. + *

+ * + *

Usage Example:

+ *
{@code
+ * // Configure a mock response with status, headers, and body
+ * mockClient.expect(HttpMethod.GET, "/api/users")
+ *     .thenReturn()
+ *     .withStatus(200)
+ *     .withHeader("Content-Type", "application/json")
+ *     .thenReturn("[{\"id\": 1, \"name\": \"John\"}]");
+ *
+ * // Create a standalone expected response
+ * ExpectedResponse response = ExpectedResponse.of(404)
+ *     .thenReturn("Not found");
+ * }
+ * + * @see MockClient + * @see Expectation + * @see Times + */ +public interface ExpectedResponse { + + /** + * Creates an independent expected response with the specified status code. + *

+ * This factory method is useful for creating test doubles rather than strict mocks, + * allowing you to create reusable response configurations outside of the mock client. + *

+ * + * @param status the HTTP response status code + * @return a new ExpectedResponse configured with the specified status + */ + static ExpectedResponse of(int status) { + return new ExpectedResponseRecord(null).withStatus(status); + } + + /** + * Adds a header to the expected response. + * + * @param key the header name + * @param value the header value + * @return this ExpectedResponse for method chaining + */ + ExpectedResponse withHeader(String key, String value); + + /** + * Adds a collection of headers to the expected response. + * + * @param headers the {@link Headers} object containing the headers to add + * @return this ExpectedResponse for method chaining + */ + ExpectedResponse withHeaders(Headers headers); + + /** + * Sets the HTTP status code of the expected response. + *

+ * The status message will be set to a default value based on the status code. + *

+ * + * @param httpStatus the HTTP status code (e.g., 200, 404, 500) + * @return this ExpectedResponse for method chaining + */ + ExpectedResponse withStatus(int httpStatus); + + /** + * Sets the HTTP status code and status message of the expected response. + * + * @param httpStatus the HTTP status code (e.g., 200, 404, 500) + * @param statusMessage the status message (e.g., "OK", "Not Found", "Internal Server Error") + * @return this ExpectedResponse for method chaining + */ + ExpectedResponse withStatus(int httpStatus, String statusMessage); + + /** + * Sets the response body as a string. + * + * @param body the response body content + * @return this ExpectedResponse for method chaining + */ + ExpectedResponse thenReturn(String body); + + /** + * Sets the response body as a JSON element. + *

+ * The JSON element will be serialized to a string when the response is built. + *

+ * + * @param jsonObject the JSON element ({@link kong.unirest.core.json.JSONObject} or {@link kong.unirest.core.json.JSONArray}) + * @return this ExpectedResponse for method chaining + */ + ExpectedResponse thenReturn(JSONElement jsonObject); + + /** + * Sets the response body as a POJO object. + *

+ * The object will be serialized using the configured object mapper when the response is built. + *

+ * + * @param pojo the object to serialize as the response body + * @return this ExpectedResponse for method chaining + */ + ExpectedResponse thenReturn(Object pojo); + + /** + * Sets a supplier for the response body. + *

+ * The supplier will be invoked at the time the response is built, allowing for + * dynamic response generation. This is useful when the response content needs + * to be determined at request time rather than at setup time. + *

+ * + * @param supplier a supplier that provides the response body string + * @return this ExpectedResponse for method chaining + */ + ExpectedResponse thenReturn(Supplier supplier); + + /** + * Verifies that the expectation associated with this response was fulfilled at least once. + *

+ * This is equivalent to calling {@code verify(Times.atLeastOnce())}. + *

+ * + * @throws UnirestAssertion if the expectation was not fulfilled + */ + void verify(); + + /** + * Verifies that the expectation associated with this response was fulfilled the specified number of times. + * + * @param times the expected invocation count (e.g., {@link Times#exactlyOnce()}, {@link Times#never()}) + * @throws UnirestAssertion if the expectation was not fulfilled the expected number of times + * @see Times + */ + void verify(Times times); +} diff --git a/unirest-modules-mocks/src/main/java/kong/unirest/core/ExpectedResponseRecord.java b/unirest-modules-mocks/src/main/java/kong/unirest/core/ExpectedResponseRecord.java new file mode 100644 index 000000000..fe5176ff6 --- /dev/null +++ b/unirest-modules-mocks/src/main/java/kong/unirest/core/ExpectedResponseRecord.java @@ -0,0 +1,130 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import kong.unirest.core.json.JSONElement; + +import java.util.function.Function; +import java.util.function.Supplier; + +class ExpectedResponseRecord implements ExpectedResponse, ResponseBuilder { + private Expectation expectation; + private Function response = o -> null; + private Headers responseHeaders = new Headers(); + private int responseStatus = 200; + private String responseText = "Ok"; + + ExpectedResponseRecord(Expectation expectation){ + this.expectation = expectation; + } + + @Override + public ExpectedResponse withHeader(String key, String value) { + this.responseHeaders.add(key, value); + return this; + } + + @Override + public ExpectedResponse withHeaders(Headers value) { + value.all().forEach(h -> withHeader(h.getName(), h.getValue())); + return this; + } + + @Override + public ExpectedResponse withStatus(int httpStatus) { + return withStatus(httpStatus,""); + } + + @Override + public ExpectedResponse withStatus(int httpStatus, String statusMessage) { + this.responseStatus = httpStatus; + this.responseText = statusMessage; + return this; + } + + @Override + public ExpectedResponse thenReturn(String body) { + this.response = o -> body; + return this; + } + + @Override + public ExpectedResponse thenReturn(JSONElement jsonObject) { + this.response = o -> jsonObject.toString(); + return this; + } + + @Override + public ExpectedResponse thenReturn(Supplier supplier){ + this.response = o -> supplier.get(); + return this; + } + + @Override + public void verify() { + verify(null); + } + + @Override + public void verify(Times times) { + if(expectation == null){ + throw new UnirestAssertion("An expectation was never invoked!"); + } + expectation.verify(times); + } + + @Override + public ExpectedResponse thenReturn(Object pojo) { + if(pojo instanceof MockResponse){ + var res = (MockResponse)pojo; + return thenReturn(res); + } else { + this.response = o -> o.writeValue(pojo); + } + return this; + } + + private ExpectedResponse thenReturn(MockResponse res) { + this.response = o -> res.getBody() == null ? null : String.valueOf(res.getBody()); + return withStatus(res.getStatus(), res.getStatusText()) + .withHeaders(res.getHeaders()); + } + + public RawResponse toRawResponse(Config config, HttpRequest request) { + return new MockRawResponse(response.apply(getObjectMapper(request, config)), + responseHeaders, responseStatus, responseText, config, request.toSummary()); + } + + private ObjectMapper getObjectMapper(HttpRequest request, Config config) { + return Util.tryCast(request, BaseRequest.class) + .map(BaseRequest::getObjectMapper) + .orElseGet(() -> config.getObjectMapper()); + } + + void setExpectation(Expectation invocation) { + this.expectation = invocation; + } +} diff --git a/unirest-modules-mocks/src/main/java/kong/unirest/core/FieldMatcher.java b/unirest-modules-mocks/src/main/java/kong/unirest/core/FieldMatcher.java new file mode 100644 index 000000000..7dc3853f7 --- /dev/null +++ b/unirest-modules-mocks/src/main/java/kong/unirest/core/FieldMatcher.java @@ -0,0 +1,94 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.net.URLEncoder; +import java.util.*; + +/** + * Matches simple form fields + */ +public class FieldMatcher implements BodyMatcher { + private final Map formParams; + + /** + * Creates a FieldMatcher expecting a map of keys and values + * use like: FieldMatcher.of("fruit", "orange", "quantity" "42") + * @param keyValuePairs an array of key-value pairs to expect + * @return a new FieldMatcher + */ + public static FieldMatcher of(String... keyValuePairs) { + return new FieldMatcher(mapOf(keyValuePairs)); + } + + /** + * Creates a FieldMatcher expecting a map of keys and values + * @param formParams the map of field params + */ + public FieldMatcher(Map formParams) { + this.formParams = formParams; + } + + @Override + public MatchStatus matches(List body) throws AssertionError { + List missing = new ArrayList<>(); + boolean pass = true; + for (Map.Entry r : formParams.entrySet()) { + String expectedParam = r.getKey() + "=" + URLEncoder.encode(r.getValue()); + if (body.stream().noneMatch(p -> Objects.equals(expectedParam, p))) { + missing.add(expectedParam); + pass = false; + } + } + return new MatchStatus(pass, description(pass, missing)); + } + + + private String description(boolean pass, List missing) { + if(pass){ + return ""; + } + StringJoiner joiner = new StringJoiner(System.lineSeparator()); + missing.forEach(m -> joiner.add(m)); + + return joiner.toString(); + } + + private static Map mapOf(Object... keyValues) { + Map map = new HashMap<>(); + + K key = null; + for (int index = 0; index < keyValues.length; index++) { + if (index % 2 == 0) { + key = (K) keyValues[index]; + } else { + map.put(key, (V) keyValues[index]); + } + } + + return map; + } +} diff --git a/unirest-modules-mocks/src/main/java/kong/unirest/core/Invocation.java b/unirest-modules-mocks/src/main/java/kong/unirest/core/Invocation.java new file mode 100644 index 000000000..179da1571 --- /dev/null +++ b/unirest-modules-mocks/src/main/java/kong/unirest/core/Invocation.java @@ -0,0 +1,322 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import kong.unirest.core.json.JSONElement; + +import java.util.*; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.lang.System.lineSeparator; + +class Invocation implements Expectation { + private Routes routes; + private List requests = new ArrayList<>(); + private Headers expectedHeaders = new Headers(); + private Headers expectedQueryParams = new Headers(); + private Boolean expected = false; + private BodyMatcher expectedBody; + private MatchStatus expectedBodyStatus; + private ExpectedResponseRecord expectedResponse = new ExpectedResponseRecord(this); + private Function, ExpectedResponse> functionalResponse = r -> expectedResponse; + private Times expectedTimes = Times.atLeastOnce(); + + public Invocation(Routes routes){ + this.routes = routes; + this.expected = true; + } + + public Invocation(Routes routes, HttpRequest request) { + this.routes = routes; + this.expectedHeaders = request.getHeaders(); + } + + Invocation(Routes routes, Invocation other) { + this.routes = routes; + this.expectedResponse = other.expectedResponse; + } + + Invocation() { + + } + + @Override + public ExpectedResponse thenReturn(String body) { + return expectedResponse.thenReturn(body); + } + + @Override + public ExpectedResponse thenReturn(JSONElement jsonObject) { + return expectedResponse.thenReturn(jsonObject); + } + + @Override + public ExpectedResponse thenReturn(Object pojo) { + return expectedResponse.thenReturn(pojo); + } + + @Override + public ExpectedResponse thenReturn(Supplier supplier) { + return expectedResponse.thenReturn(supplier); + } + + @Override + public void thenReturn(Function, ExpectedResponse> fun) { + this.functionalResponse = fun; + } + + RawResponse getResponse(Config config, HttpRequest request) { + return tryCast(functionalResponse.apply(request), ExpectedResponseRecord.class) + .map(e -> { + e.setExpectation(this); + return e.toRawResponse(config, request); + }) + .orElseThrow(() -> new UnirestException("No Result Configured For Response")); + } + + private Headers allHeaders() { + return requests.stream() + .map(i -> i.getHeaders()) + .reduce((h1, h2) -> { + h1.putAll(h2); + return h1; + }) + .orElseGet(Headers::new); + } + + public void log(HttpRequest request) { + this.requests.add(request); + } + + @Override + public Expectation header(String key, String value) { + expectedHeaders.add(key, value); + return this; + } + + @Override + public Expectation queryString(String key, String value) { + expectedQueryParams.add(key, value); + return this; + } + + @Override + public Expectation body(String body) { + expectedBody = new EqualsBodyMatcher(body); + return this; + } + + @Override + public Expectation body(BodyMatcher matcher) { + expectedBody = matcher; + return this; + } + + @Override + public ExpectedResponse thenReturn() { + return expectedResponse; + } + + @Override + public void verify() { + verify(null); + } + + @Override + public void verify(Times times) { + var thisTime = times == null ? expectedTimes : times; + var match = thisTime.matches(requests.size()); + if(!match.isSuccess()){ + throw new UnirestAssertion("%s\n%s", match.getMessage(), details()); + } + } + + @Override + public Expectation times(Times times) { + this.expectedTimes = times; + return this; + } + + private String details() { + var sb = new StringBuilder(routes.getMethod().name()) + .append(" ") + .append(routes.getPath()).append(lineSeparator()); + if(expectedHeaders.size() > 0){ + sb.append("Headers:\n"); + sb.append(expectedHeaders); + } + if(expectedQueryParams.size() > 0){ + sb.append("Params:\n"); + sb.append(expectedQueryParams); + } + if(expectedBody != null){ + sb.append("Body:\n"); + if(expectedBodyStatus != null) { + sb.append("\t" + expectedBodyStatus.getDescription()); + } else { + sb.append("\t null"); + } + } + return sb.toString(); + } + + + public boolean hasExpectedHeader(String key, String value) { + Headers allH = allHeaders(); + return allH.containsKey(key) && allH.get(key).contains(value); + } + + private Stream getBodyStream() { + return requests.stream() + .filter(r -> r.getBody().isPresent()) + .map(r -> (Body) r.getBody().get()); + } + + public boolean hasBody(String body){ + return getBodyStream() + .anyMatch(b -> uniBodyMatches(body, b)); + } + + private boolean uniBodyMatches(String body, Body o) { + return tryCast(o, HttpRequestUniBody.class) + .map(h -> h.uniPart()) + .map(u -> u.getValue().equals(body)) + .orElse(false); + } + + public boolean hasField(String name, String value) { + return getBodyStream() + .anyMatch(b -> hasField(name, value, b)); + } + + private boolean hasField(String name, String value, Body b) { + return tryCast(b, HttpRequestMultiPart.class) + .map(h -> h.multiParts()) + .orElse(Collections.emptyList()) + .stream() + .anyMatch(part -> part.getName().equals(name) && part.getValue().equals(value)); + } + + public Integer requestSize() { + return requests.size(); + } + + public List getRequests() { + return requests; + } + + public Boolean isExpected() { + return expected; + } + + public Integer scoreMatch(HttpRequest request) { + int score = 0; + score += scoreHeaders(request); + score += scoreQuery(request); + score += scoreBody(request); + return score; + } + + private int scoreBody(HttpRequest request) { + if(expectedBody != null){ + return tryCast(request, Body.class) + .map(this::matchBody) + .orElse(-1000); + } + return 0; + } + + private Integer matchBody(Body b) { + if(b.isEntityBody()){ + BodyPart bodyPart = b.uniPart(); + if(bodyPart == null && expectedBody == null){ + return 1; + } else if(String.class.isAssignableFrom(bodyPart.getPartType())){ + expectedBodyStatus = expectedBody.matches(Arrays.asList((String) bodyPart.getValue())); + if(expectedBodyStatus.isSuccess()){ + return 1; + } else { + return -1000; + } + } else if (expectedBody != null) { + return -1000; + } + } else { + List parts = b.multiParts().stream().map(p -> p.toString()).collect(Collectors.toList()); + if(parts.isEmpty() && expectedBody == null){ + return 1; + } else { + expectedBodyStatus = expectedBody.matches(parts); + if(expectedBodyStatus.isSuccess()){ + return 1; + } else { + return -1000; + } + } + } + return 0; + } + + private int scoreHeaders(HttpRequest request) { + if(expectedHeaders.size() > 0){ + long b = expectedHeaders.all().stream().filter(h -> + request.getHeaders().get(h.getName()).contains(h.getValue())) + .count(); + + if(b != expectedHeaders.size()){ + return -1000; + } + return Long.valueOf(b).intValue(); + } + return 0; + } + + private int scoreQuery(HttpRequest request) { + if(expectedQueryParams.size() > 0){ + QueryParams p = QueryParams.fromURI(request.getUrl()); + long b = expectedQueryParams.all().stream().filter(h -> + p.getQueryParams().stream().anyMatch(q -> q.getName().equalsIgnoreCase(h.getName()) + && q.getValue().equalsIgnoreCase(h.getValue()))) + .count(); + if(b != expectedQueryParams.size()){ + return -1000; + } + return Long.valueOf(b).intValue(); + } + return 0; + } + + private static Optional tryCast(T original, Class too) { + if (original != null && too.isAssignableFrom(original.getClass())) { + return Optional.of((M) original); + } + return Optional.empty(); + } + +} diff --git a/unirest-modules-mocks/src/main/java/kong/unirest/core/MatchStatus.java b/unirest-modules-mocks/src/main/java/kong/unirest/core/MatchStatus.java new file mode 100644 index 000000000..c80311c2b --- /dev/null +++ b/unirest-modules-mocks/src/main/java/kong/unirest/core/MatchStatus.java @@ -0,0 +1,78 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +/** + * Represents the result of a body matching operation. + *

+ * This class encapsulates whether a {@link BodyMatcher} successfully matched the request body, + * along with a description that can be used for error reporting when the match fails. + *

+ *

+ * Typically returned by {@link BodyMatcher#matches(java.util.List)} to indicate + * whether the actual request body matched the expected pattern. + *

+ * + * @see BodyMatcher + * @see EqualsBodyMatcher + */ +public class MatchStatus { + private final boolean isSuccess; + private final String description; + + /** + * Constructs a new MatchStatus with the specified success flag and description. + * + * @param isSuccess {@code true} if the match was successful, {@code false} otherwise + * @param description a description of what was expected; used for error messages when matching fails + */ + public MatchStatus(boolean isSuccess, String description) { + this.isSuccess = isSuccess; + this.description = description; + } + + /** + * Returns whether the match was successful. + * + * @return {@code true} if the body matched the expected pattern, {@code false} otherwise + */ + public boolean isSuccess(){ + return isSuccess; + } + + /** + * Returns the description associated with this match status. + *

+ * This typically contains the expected value or pattern, which is useful + * for generating meaningful error messages when the match fails. + *

+ * + * @return the description, or {@code null} if none was provided + */ + public String getDescription() { + return description; + } +} diff --git a/unirest-modules-mocks/src/main/java/kong/unirest/core/Matchers.java b/unirest-modules-mocks/src/main/java/kong/unirest/core/Matchers.java new file mode 100644 index 000000000..cba7e00af --- /dev/null +++ b/unirest-modules-mocks/src/main/java/kong/unirest/core/Matchers.java @@ -0,0 +1,42 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +/** + * Static set of Matchers + */ +public class Matchers { + + /** + * Creates a FieldMatcher expecting a map of keys and values + * use like: FieldMatcher.of("fruit", "orange", "quantity" "42") + * @param keyValuePairs an array of key-value pairs to expect + * @return a new FieldMatcher + */ + public BodyMatcher bodyFields(String keyValuePairs){ + return FieldMatcher.of(keyValuePairs); + } +} diff --git a/unirest-modules-mocks/src/main/java/kong/unirest/core/MockClient.java b/unirest-modules-mocks/src/main/java/kong/unirest/core/MockClient.java new file mode 100644 index 000000000..f72427512 --- /dev/null +++ b/unirest-modules-mocks/src/main/java/kong/unirest/core/MockClient.java @@ -0,0 +1,374 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + + +import kong.unirest.core.java.Event; + +import java.net.http.WebSocket; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import static java.util.concurrent.CompletableFuture.completedFuture; + +/** + * A mock HTTP client for testing Unirest-based code without making real network requests. + *

+ * MockClient implements the {@link Client} interface and can be registered with a Unirest instance + * to intercept HTTP requests. It supports both synchronous and asynchronous request handling, + * as well as WebSocket connections. + *

+ * + *

Key Features:

+ *
    + *
  • Define expected requests and their responses using the fluent expectation API
  • + *
  • Verify that expected requests were made with specific parameters
  • + *
  • Simulate WebSocket connections for testing real-time communication
  • + *
  • Configure default responses for unexpected requests
  • + *
+ * + *

Usage Example:

+ *
{@code
+ * // Register the mock client
+ * MockClient mock = MockClient.register();
+ *
+ * // Set up expectations
+ * mock.expect(HttpMethod.GET, "/api/users")
+ *     .thenReturn()
+ *     .withStatus(200)
+ *     .withBody("[{\"id\": 1, \"name\": \"John\"}]");
+ *
+ * // Make requests using Unirest (they will be intercepted by the mock)
+ * HttpResponse response = Unirest.get("/api/users").asString();
+ *
+ * // Verify expectations
+ * mock.assertThat(HttpMethod.GET, "/api/users").wasInvokedTimes(1);
+ * mock.verifyAll();
+ *
+ * // Clean up
+ * mock.reset();
+ * }
+ * + * @see Client + * @see Expectation + * @see Assert + */ +public class MockClient implements Client { + private final Supplier config; + private List routes = new ArrayList<>(); + private SocketSet remoteSocket; + private Invocation defaultResponse; + + /** + * Creates a new MockClient with the specified configuration supplier. + * + * @param config a supplier that provides the Unirest configuration + */ + public MockClient(Supplier config){ + this.config = config; + } + + /** + * Creates a new MockClient and registers it on the primary static UnirestInstance. + *

+ * This is a convenience method for quickly setting up mocking in tests that use + * the static {@link Unirest} methods. + *

+ * + * @return the newly created and registered MockClient + */ + public static MockClient register() { + return register(Unirest.primaryInstance()); + } + + /** + * Creates a new MockClient and registers it on the specified Unirest instance. + *

+ * After registration, all HTTP requests made through the provided instance + * will be intercepted by this mock client. + *

+ * + * @param unirest the Unirest instance to register the mock client with + * @return the newly created and registered MockClient + */ + public static MockClient register(UnirestInstance unirest) { + MockClient client = new MockClient(unirest::config); + unirest.config().httpClient(client); + return client; + } + + /** + * Clears any MockClient from the primary Unirest instance. + *

+ * This restores the primary instance to use the default HTTP client. + *

+ */ + public static void clear() { + clear(Unirest.primaryInstance()); + } + + /** + * Clears any MockClient from the specified Unirest instance. + *

+ * This restores the instance to use the default HTTP client. + * Only clears the client if the current client is a MockClient. + *

+ * + * @param unirest the instance to clear the mock client from + */ + public static void clear(UnirestInstance unirest) { + if(unirest.config().getClient() instanceof MockClient){ + unirest.config().httpClient((Client) null); + } + } + + /** + * {@inheritDoc} + *

+ * Executes the request against configured expectations and returns a mocked response. + * If no matching expectation is found, a new route is created with the default response. + *

+ */ + @Override + public HttpResponse request(HttpRequest request, Function> transformer, Class resultType) { + Routes exp = findExpectation(request); + Config c = this.config.get(); + c.getUniInterceptor().onRequest(request, c); + MetricContext metric = c.getMetric().begin(request.toSummary()); + RawResponse response = exp.exchange(request, c); + metric.complete(new ResponseSummary(response), null); + HttpResponse rez = transformer.apply(response); + c.getUniInterceptor().onResponse(rez, request.toSummary(), c); + return rez; + } + + private Routes findExpectation(HttpRequest request) { + return routes.stream() + .filter(e -> e.matches(request)) + .findFirst() + .orElseGet(() -> createNewPath(request)); + } + + private Routes createNewPath(HttpRequest request) { + Routes p = new Routes(request, defaultResponse); + routes.add(p); + return p; + } + + /** + * {@inheritDoc} + *

+ * Executes the request asynchronously against configured expectations. + * The request is processed in a separate thread using {@link CompletableFuture#supplyAsync}. + *

+ */ + @Override + public CompletableFuture> request(HttpRequest request, + Function> transformer, + CompletableFuture> callback, + Class resultTypes) { + return CompletableFuture.supplyAsync(() -> request(request, transformer, resultTypes)); + } + + /** + * {@inheritDoc} + *

+ * Creates a mock WebSocket connection with both client and server sides available for testing. + * The server-side socket can be accessed via {@link #serversSocket()} to simulate + * server-initiated messages. + *

+ */ + @Override + public WebSocketResponse websocket(WebSocketRequest request, WebSocket.Listener listener) { + MockWebSocket clientSocket = new MockWebSocket(); + WebSocket.Listener clientListener = listener; + + MockWebSocket serverWebSocket = new MockWebSocket(); + MockListener serverListener = new MockListener(); + + remoteSocket = new SocketSet(serverWebSocket, serverListener, "server"); + clientSocket.init(remoteSocket); + serverWebSocket.init(new SocketSet(clientSocket, clientListener, "client")); + + return new WebSocketResponse(completedFuture(clientSocket), clientListener); + } + + /** + * {@inheritDoc} + *

+ * Note: SSE support is not yet implemented in the mock client. + * This method currently returns {@code null}. + *

+ */ + @Override + public CompletableFuture sse(SseRequest request, SseHandler handler) { + return null; + } + + /** + * {@inheritDoc} + *

+ * Note: SSE support is not yet implemented in the mock client. + * This method currently returns an empty stream. + *

+ */ + @Override + public Stream sse(SseRequest request) { + return Stream.empty(); + } + + /** + * Returns the server-side socket set for the most recent WebSocket connection. + *

+ * This allows tests to simulate server behavior by sending messages from the server + * side of the WebSocket connection. + *

+ * + * @return the server-side {@link SocketSet} containing the mock WebSocket and listener + * @throws UnirestException if no WebSocket connection has been established + */ + public SocketSet serversSocket() { + if(remoteSocket == null){ + throw new UnirestException("No Socket Yet Established"); + } + return remoteSocket; + } + + + + /** + * {@inheritDoc} + * + * @return this MockClient instance + */ + @Override + public Object getClient() { + return this; + } + + /** + * Starts an expectation chain for a specific HTTP method and path. + *

+ * Use this method to define what response should be returned when a matching + * request is made. + *

+ * + * @param method the HTTP method to match (GET, POST, etc.) + * @param path the URL path to match (can include path parameters) + * @return an {@link Expectation} which can have additional criteria and response configuration added + */ + public Expectation expect(HttpMethod method, String path) { + Path p = new Path(path); + Routes exp = findByPath(method, p).orElseGet(() -> new Routes(method, p)); + if(!this.routes.contains(exp)) { + this.routes.add(exp); + } + return exp.newExpectation(); + } + + /** + * Starts an expectation chain for any request with the specified HTTP method. + *

+ * This matches any path, useful when you only care about the HTTP method. + *

+ * + * @param method the HTTP method to match + * @return an {@link Expectation} which can have additional criteria and response configuration added + */ + public Expectation expect(HttpMethod method) { + return expect(method, null); + } + + /** + * Creates an assertion for verifying a specific method and path were invoked. + *

+ * Use this to verify that expected requests were made during a test. + *

+ * + * @param method the HTTP method to verify + * @param path the URL path to verify + * @return an {@link Assert} object for chaining additional verification criteria + * @throws UnirestAssertion if no matching invocation was found + */ + public Assert assertThat(HttpMethod method, String path) { + return findByPath(method, new Path(path)) + .orElseThrow(() -> new UnirestAssertion(String.format("No Matching Invocation:: %s %s", method, path))); + } + + private Optional findByPath(HttpMethod get, Path path) { + return routes.stream() + .filter(e -> e.matches(get, path)) + .findFirst(); + } + + /** + * Verifies that all configured expectations were invoked. + *

+ * This checks that every expectation set up with {@link #expect(HttpMethod, String)} + * was actually called during the test. + *

+ * + * @throws UnirestAssertion if any expectation was not satisfied + */ + public void verifyAll() { + routes.forEach(Routes::verifyAll); + } + + /** + * Resets all expectations and clears the default response. + *

+ * Call this between tests to ensure a clean state. This clears: + *

+ *
    + *
  • All configured expectations
  • + *
  • All recorded invocations
  • + *
  • The default response
  • + *
+ */ + public void reset() { + routes.clear(); + defaultResponse = null; + } + + /** + * Configures a default response for requests that don't match any expectation. + *

+ * If a request is made that doesn't match any configured expectation, + * this response will be returned instead of throwing an error. + *

+ * + * @return an {@link ExpectedResponse} for configuring the default response details + */ + public ExpectedResponse defaultResponse() { + this.defaultResponse = new Invocation(); + return this.defaultResponse.thenReturn(); + } +} diff --git a/unirest-modules-mocks/src/main/java/kong/unirest/core/MockConfig.java b/unirest-modules-mocks/src/main/java/kong/unirest/core/MockConfig.java new file mode 100644 index 000000000..0e290a818 --- /dev/null +++ b/unirest-modules-mocks/src/main/java/kong/unirest/core/MockConfig.java @@ -0,0 +1,72 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +/** + * A mock configuration that uses a {@link MockClient} instead of a real HTTP client. + *

+ * This class extends {@link Config} and automatically creates a {@link MockClient} + * as its HTTP client, allowing you to use all of Unirest's configuration options + * without making actual network requests. + *

+ *

+ * Use this class when you need a standalone mock configuration, for example when + * creating a {@link UnirestInstance} specifically for testing. + *

+ * + *

Usage Example:

+ *
{@code
+ * // Create a mock configuration
+ * MockConfig config = new MockConfig();
+ *
+ * // Use it with a UnirestInstance
+ * UnirestInstance unirest = new UnirestInstance(config);
+ *
+ * // Access the mock client to set up expectations
+ * MockClient mockClient = (MockClient) config.getClient();
+ * mockClient.expect(HttpMethod.GET, "/api/users")
+ *     .thenReturn("{\"id\": 1}")
+ *     .withStatus(200);
+ * }
+ * + * @see Config + * @see MockClient + * @see UnirestInstance + */ +public class MockConfig extends Config { + private MockClient client = new MockClient(() -> this); + + + /** + * {@inheritDoc} + * + * @return the {@link MockClient} instance associated with this configuration + */ + @Override + public Client getClient() { + return client; + } +} diff --git a/unirest-modules-mocks/src/main/java/kong/unirest/core/MockListener.java b/unirest-modules-mocks/src/main/java/kong/unirest/core/MockListener.java new file mode 100644 index 000000000..a9366895f --- /dev/null +++ b/unirest-modules-mocks/src/main/java/kong/unirest/core/MockListener.java @@ -0,0 +1,246 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.net.http.WebSocket; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletionStage; + +/** + * A mock implementation of {@link WebSocket.Listener} that records WebSocket messages and provides assertion methods. + *

+ * This class is used in conjunction with {@link MockClient} to test WebSocket communication. + * It records all incoming messages (text, binary, ping, pong) and connection events (open, close, error), + * allowing tests to verify that expected WebSocket interactions occurred. + *

+ * + *

Usage Example:

+ *
{@code
+ * // Get the server-side socket from the mock client
+ * SocketSet serverSocket = mockClient.serversSocket();
+ * MockListener serverListener = serverSocket.getListener();
+ *
+ * // Send a message from the client side
+ * clientWebSocket.sendText("Hello Server", true);
+ *
+ * // Assert the server received the message
+ * serverListener.assertReceivedMessage("Hello Server", true);
+ * serverListener.assertIsOpen();
+ * }
+ * + * @see WebSocket.Listener + * @see MockClient + * @see MockWebSocket + */ +public class MockListener implements WebSocket.Listener { + private List messagesReceived = new ArrayList<>(); + private ByteBuffer ping; + private ByteBuffer pong; + private boolean open = false; + private int closedStatus; + private String closedMessage; + + /** + * {@inheritDoc} + *

+ * Records that the WebSocket connection has been opened and marks this listener as open. + *

+ */ + @Override + public void onOpen(WebSocket webSocket) { + open = true; + WebSocket.Listener.super.onOpen(webSocket); + } + + /** + * {@inheritDoc} + *

+ * Records the received text message for later assertion via {@link #assertReceivedMessage(Object, boolean)}. + *

+ */ + @Override + public CompletionStage onText(WebSocket webSocket, CharSequence data, boolean last) { + messagesReceived.add(new Message(String.valueOf(data), last)); + return WebSocket.Listener.super.onText(webSocket, data, last); + } + + /** + * {@inheritDoc} + *

+ * Records the received binary message for later assertion via {@link #assertReceivedMessage(Object, boolean)}. + *

+ */ + @Override + public CompletionStage onBinary(WebSocket webSocket, ByteBuffer data, boolean last) { + messagesReceived.add(new Message(data, last)); + return WebSocket.Listener.super.onBinary(webSocket, data, last); + } + + /** + * {@inheritDoc} + *

+ * Records the ping message and automatically sends a pong response. + * The ping message can be verified using {@link #assertPing(ByteBuffer)}. + *

+ */ + @Override + public CompletionStage onPing(WebSocket webSocket, ByteBuffer message) { + ping = message; + webSocket.sendPong(message); + return WebSocket.Listener.super.onPing(webSocket, message); + } + + /** + * {@inheritDoc} + *

+ * Records the pong message for later assertion via {@link #assertPong(ByteBuffer)}. + *

+ */ + @Override + public CompletionStage onPong(WebSocket webSocket, ByteBuffer message) { + this.pong = message; + return WebSocket.Listener.super.onPong(webSocket, message); + } + + /** + * {@inheritDoc} + *

+ * Records the close event including status code and reason. + * The close can be verified using {@link #assertIsClosed(int, String)}. + *

+ */ + @Override + public CompletionStage onClose(WebSocket webSocket, int statusCode, String reason) { + open = false; + closedStatus = statusCode; + closedMessage = reason; + return WebSocket.Listener.super.onClose(webSocket, statusCode, reason); + } + + /** + * {@inheritDoc} + *

+ * Delegates to the default error handling behavior. + *

+ */ + @Override + public void onError(WebSocket webSocket, Throwable error) { + WebSocket.Listener.super.onError(webSocket, error); + } + + /** + * Asserts that a specific message was received by this listener. + *

+ * This method checks both text and binary messages that have been recorded. + *

+ * + * @param message the expected message content (String for text messages, ByteBuffer for binary) + * @param last whether this was expected to be the last message in a sequence + * @throws UnirestAssertion if no matching message was received + */ + public void assertReceivedMessage(Object message, boolean last) { + if(!messagesReceived.stream().anyMatch(e -> Objects.equals(e.data, message) && Objects.equals(e.last, last))){ + throw new UnirestAssertion("Did not receive any message: [%s : %s] ", message, last); + } + } + + /** + * Asserts that the WebSocket connection was closed with the expected status code and message. + * + * @param status the expected close status code + * @param message the expected close reason message + * @throws UnirestAssertion if the connection is still open or was closed with different status/message + */ + public void assertIsClosed(int status, String message) { + if(open){ + throw new UnirestAssertion("Expected to be closed but was not"); + } else if (closedStatus != status || !Objects.equals(closedMessage, message)){ + throw new UnirestAssertion("Incorrect Closed Status/Message. Expected [%s : %s] but got [%s : %s]", + status, message, closedStatus, closedMessage); + } + } + + /** + * Asserts that a ping message was received with the expected content. + *

+ * Note that the {@link #onPing(WebSocket, ByteBuffer)} method automatically sends a pong + * response to the WebSocket when a ping is received. + *

+ * + * @param message the expected ping message content + * @throws UnirestAssertion if no ping was received or the content doesn't match + */ + public void assertPing(ByteBuffer message) { + if(!Objects.equals(ping, message)){ + throw new UnirestAssertion("Expected Ping Call with buffer %s but got %s", message, ping); + } + } + + /** + * Asserts that a pong message was received with the expected content. + * + * @param message the expected pong message content + * @throws UnirestAssertion if no pong was received or the content doesn't match + */ + public void assertPong(ByteBuffer message) { + if(!message.equals(pong)){ + throw new UnirestAssertion("Expected Pong Message %s but got %s", message, pong); + } + } + + /** + * Asserts that the WebSocket connection is currently open. + * + * @throws UnirestAssertion if the connection is closed + */ + public void assertIsOpen() { + if(!open){ + throw new UnirestAssertion("Expected socket to be open but was closed."); + } + } + + /** + * Internal class representing a recorded WebSocket message. + */ + private class Message { + private final Object data; + private final boolean last; + + /** + * Creates a new Message record. + * + * @param data the message content (String or ByteBuffer) + * @param last whether this is the last message in a sequence + */ + public Message(Object data, boolean last) { + this.data = data; + this.last = last; + } + } +} diff --git a/unirest-modules-mocks/src/main/java/kong/unirest/core/MockRawResponse.java b/unirest-modules-mocks/src/main/java/kong/unirest/core/MockRawResponse.java new file mode 100644 index 000000000..b0b5123b5 --- /dev/null +++ b/unirest-modules-mocks/src/main/java/kong/unirest/core/MockRawResponse.java @@ -0,0 +1,219 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Objects; + +/** + * A mock implementation of {@link RawResponse} for testing purposes. + *

+ * This class provides a simulated HTTP response that can be configured with + * custom status codes, headers, and body content. It is used internally by + * {@link MockClient} to return predefined responses for mocked requests. + *

+ *

+ * The response body is stored as a string and converted to the appropriate + * format (bytes, stream, reader) when requested. + *

+ * + * @see RawResponse + * @see MockClient + */ +public class MockRawResponse implements RawResponse { + private final String response; + private final Headers responseHeaders; + private final int status; + private final String statusMessage; + private final Config config; + private final HttpRequestSummary summary; + + /** + * Constructs a new MockRawResponse with the specified response details. + * + * @param responseBody the response body content as a string, or {@code null} for no content + * @param responseHeaders the response headers + * @param status the HTTP status code + * @param statusMessage the HTTP status message (e.g., "OK", "Not Found") + * @param config the Unirest configuration + * @param summary the summary of the original request + */ + public MockRawResponse(String responseBody, Headers responseHeaders, int status, + String statusMessage, Config config, HttpRequestSummary summary) { + this.response = responseBody; + this.responseHeaders = responseHeaders; + this.status = status; + this.statusMessage = statusMessage; + this.config = config; + this.summary = summary; + } + + /** + * {@inheritDoc} + */ + @Override + public int getStatus() { + return status; + } + + /** + * {@inheritDoc} + */ + @Override + public String getStatusText() { + return statusMessage; + } + + /** + * {@inheritDoc} + */ + @Override + public Headers getHeaders() { + return responseHeaders; + } + + /** + * {@inheritDoc} + *

+ * Returns the response body as a new {@link ByteArrayInputStream}. + *

+ */ + @Override + public InputStream getContent() { + return new ByteArrayInputStream(response.getBytes()); + } + + /** + * {@inheritDoc} + */ + @Override + public byte[] getContentAsBytes() { + return response.getBytes(); + } + + /** + * {@inheritDoc} + */ + @Override + public String getContentAsString() { + return response; + } + + /** + * {@inheritDoc} + *

+ * If the charset is {@code null}, defaults to UTF-8. + *

+ */ + @Override + public String getContentAsString(String charset) { + if(Objects.isNull(response)){ + return null; + } + return new String(response.getBytes(), tryGetCharset(charset)); + } + + /** + * Attempts to get a Charset from the provided name. + * + * @param charset the charset name, or {@code null} + * @return the corresponding Charset, or UTF-8 if charset is {@code null} + */ + private Charset tryGetCharset(String charset) { + if(Objects.isNull(charset)){ + return StandardCharsets.UTF_8; + } + return Charset.forName(charset); + } + + /** + * {@inheritDoc} + */ + @Override + public InputStreamReader getContentReader() { + return new InputStreamReader(getContent()); + } + + /** + * {@inheritDoc} + * + * @return {@code true} if the response body is not {@code null} + */ + @Override + public boolean hasContent() { + return response != null; + } + + /** + * {@inheritDoc} + *

+ * Returns the value of the "Content-Type" header. + *

+ */ + @Override + public String getContentType() { + return responseHeaders.getFirst("Content-Type"); + } + + /** + * {@inheritDoc} + *

+ * Returns the value of the "Content-Encoding" header. + *

+ */ + @Override + public String getEncoding() { + return responseHeaders.getFirst("Content-Encoding"); + } + + /** + * {@inheritDoc} + */ + @Override + public Config getConfig() { + return config; + } + + /** + * {@inheritDoc} + */ + @Override + public HttpResponseSummary toSummary() { + return new ResponseSummary(this); + } + + /** + * {@inheritDoc} + */ + @Override + public HttpRequestSummary getRequestSummary() { + return summary; + } +} diff --git a/unirest-modules-mocks/src/main/java/kong/unirest/core/MockResponse.java b/unirest-modules-mocks/src/main/java/kong/unirest/core/MockResponse.java new file mode 100644 index 000000000..5b5d0fd8b --- /dev/null +++ b/unirest-modules-mocks/src/main/java/kong/unirest/core/MockResponse.java @@ -0,0 +1,172 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.util.Optional; +import java.util.function.Consumer; + +/** + * A Mock Response that can be used in testing. + * @param the body type + */ +public class MockResponse extends BaseResponse { + private final MockConfig mockConfig; + private Optional ex = Optional.empty(); + private final T body; + + /** + * Construct a mock Response + * @param status the status of the response + * @param statusText the status text + * @param body the body + */ + public MockResponse(int status, String statusText, T body){ + this(status, statusText, body, new MockConfig()); + } + + /** + * Construct a mock Response + * @param status the status of the response + * @param statusText the status text + * @param body the body + * @param config a mockConfig for post-processing options + */ + public MockResponse(int status, String statusText, T body, MockConfig config){ + super(new MockRawResponse( + "", new Headers(), status, statusText, config, null + )); + this.mockConfig = config; + this.body = body; + } + + /** + * Construct a simple successful (200 ok) response with a body + * @param body the body + * @param the type of body + * @return a MockResponse; + */ + public static MockResponse ok(T body) { + return new MockResponse<>(200, "ok", body); + } + + /** + * Construct a simple failed (400 bad request) response with a body + * @param body the body + * @param the type of body + * @return a MockResponse; + */ + public static MockResponse bad(T body) { + return new MockResponse<>(400, "bad request", body); + } + + /** + * Construct a response with a status and body. The status text is just the string of the status + * @param status the status + * @param body the body + * @param the type of body + * @return a MockResponse; + */ + public static MockResponse of(int status, T body) { + return new MockResponse<>(status, String.valueOf(status), body); + } + + /** + * Construct a response with a status and body. The status text is just the string of the status + * @param status the status + * @param statusText the status text + * @param body the body + * @param the type of body + * @return a MockResponse; + */ + public static MockResponse of(int status, String statusText, T body) { + return new MockResponse<>(status, statusText, body); + } + + /** + * get the MockConfig for this MockResponse + * @return the config + */ + public MockConfig config(){ + return mockConfig; + } + + + @Override + public T getBody() { + return body; + } + + @Override + protected String getRawBody() { + return String.valueOf(body); + } + + @Override + public Optional getParsingError() { + return ex; + } + + /** + * add a header value to the response + * @param key the header key + * @param value the header value + * @return this MockResponse + */ + public MockResponse withHeader(String key, String value) { + getHeaders().add(key, value); + return this; + } + + /** + * Flag that there was a post-processing parsing error with the object Mapper + * this jams the body as a string into the parsing exception and sets a generic oops exception + * @return this MockResponse + */ + public MockResponse failedToParse() { + return failedToParse(new Exception("oops"), String.valueOf(body)); + } + + /** + * Flag that there was a post-processing parsing error with the object Mapper + * @param e the exception thrown + * @param originalBody the original body before the object mapper got involved. + * @return this MockResponse + */ + public MockResponse failedToParse(Exception e, String originalBody) { + ex = Optional.of(new UnirestParsingException(originalBody, e)); + return this; + } + + /** + * Set some options on the current MockConfig. + * @param c a Consumer with options to set on the config + * @return this MockResponse + */ + public MockResponse withConfigOptions(Consumer c) { + c.accept(mockConfig); + return this; + } +} diff --git a/unirest-modules-mocks/src/main/java/kong/unirest/core/MockWebSocket.java b/unirest-modules-mocks/src/main/java/kong/unirest/core/MockWebSocket.java new file mode 100644 index 000000000..34ee1d88e --- /dev/null +++ b/unirest-modules-mocks/src/main/java/kong/unirest/core/MockWebSocket.java @@ -0,0 +1,197 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.net.http.WebSocket; +import java.nio.ByteBuffer; +import java.util.concurrent.CompletableFuture; +import java.util.function.BiConsumer; + +/** + * A mock implementation of {@link WebSocket} for testing WebSocket communication. + *

+ * This class simulates a WebSocket connection by directly sending messages to a paired + * listener on the "other side" of the connection. When a message is sent through this + * mock WebSocket, it is immediately delivered to the remote listener, simulating + * network communication without actual network calls. + *

+ *

+ * MockWebSocket instances are used in pairs (client and server) connected via + * {@link SocketSet} objects. Each socket sends messages directly to the other's listener. + *

+ * + *

Usage:

+ *

+ * This class is typically used internally by {@link MockClient#websocket} and should not + * need to be instantiated directly. Access the server-side socket through + * {@link MockClient#serversSocket()} to simulate server behavior in tests. + *

+ * + * @see WebSocket + * @see MockClient + * @see MockListener + * @see SocketSet + */ +public class MockWebSocket implements WebSocket { + private SocketSet remoteSocketSet; + + /** + * Sends a message to the remote listener by invoking the provided consumer. + * + * @param consumer a bi-consumer that receives the remote WebSocket and Listener + * @return a completed {@link CompletableFuture} containing this WebSocket + * @throws UnirestAssertion if this socket has not been initialized with {@link #init(SocketSet)} + */ + private CompletableFuture sendToOtherSide(BiConsumer consumer){ + if(remoteSocketSet == null){ + throw new UnirestAssertion("Socket is not initialized. Make sure to call init(SocketSet) with the remote set."); + } + consumer.accept(remoteSocketSet.getSocket(), remoteSocketSet.getListener()); + return CompletableFuture.completedFuture(this); + } + + /** + * {@inheritDoc} + *

+ * Sends the text message directly to the remote listener's {@code onText} method. + *

+ */ + @Override + public CompletableFuture sendText(CharSequence data, boolean last) { + return sendToOtherSide((s,l) -> l.onText(s, data, last)); + } + + /** + * {@inheritDoc} + *

+ * Sends the binary message directly to the remote listener's {@code onBinary} method. + *

+ */ + @Override + public CompletableFuture sendBinary(ByteBuffer data, boolean last) { + return sendToOtherSide((s,l) -> l.onBinary(s, data, last)); + } + + /** + * {@inheritDoc} + *

+ * Sends the ping message directly to the remote listener's {@code onPing} method. + *

+ */ + @Override + public CompletableFuture sendPing(ByteBuffer message) { + return sendToOtherSide((s,l) -> l.onPing(s, message)); + } + + /** + * {@inheritDoc} + *

+ * Sends the pong message directly to the remote listener's {@code onPong} method. + *

+ */ + @Override + public CompletableFuture sendPong(ByteBuffer message) { + return sendToOtherSide((s,l) -> l.onPong(s, message)); + } + + /** + * {@inheritDoc} + *

+ * Sends the close message directly to the remote listener's {@code onClose} method. + *

+ */ + @Override + public CompletableFuture sendClose(int statusCode, String reason) { + return sendToOtherSide((s,l) -> l.onClose(s, statusCode, reason)); + } + + /** + * {@inheritDoc} + *

+ * This implementation is a no-op in the mock. + *

+ */ + @Override + public void request(long n) { + + } + + /** + * {@inheritDoc} + * + * @return always {@code null} in this mock implementation + */ + @Override + public String getSubprotocol() { + return null; + } + + /** + * {@inheritDoc} + * + * @return always {@code false} in this mock implementation + */ + @Override + public boolean isOutputClosed() { + return false; + } + + /** + * {@inheritDoc} + * + * @return always {@code false} in this mock implementation + */ + @Override + public boolean isInputClosed() { + return false; + } + + /** + * {@inheritDoc} + *

+ * This implementation is a no-op in the mock. + *

+ */ + @Override + public void abort() { + + } + + /** + * Initializes this WebSocket with a connection to the other side. + *

+ * This method establishes the pairing between this WebSocket and the remote + * {@link SocketSet}, enabling bidirectional communication. It also triggers + * the {@code onOpen} event on the remote listener. + *

+ * + * @param otherSide the {@link SocketSet} representing the remote endpoint + */ + public void init(SocketSet otherSide) { + this.remoteSocketSet = otherSide; + otherSide.open(); + } +} diff --git a/unirest-modules-mocks/src/main/java/kong/unirest/core/ResponseBuilder.java b/unirest-modules-mocks/src/main/java/kong/unirest/core/ResponseBuilder.java new file mode 100644 index 000000000..cbe10af63 --- /dev/null +++ b/unirest-modules-mocks/src/main/java/kong/unirest/core/ResponseBuilder.java @@ -0,0 +1,30 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +interface ResponseBuilder { + RawResponse toRawResponse(Config config, HttpRequest request); +} diff --git a/unirest-modules-mocks/src/main/java/kong/unirest/core/Routes.java b/unirest-modules-mocks/src/main/java/kong/unirest/core/Routes.java new file mode 100644 index 000000000..ff24280a5 --- /dev/null +++ b/unirest-modules-mocks/src/main/java/kong/unirest/core/Routes.java @@ -0,0 +1,205 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.util.*; + +class Routes implements Assert { + private final String path; + private final HttpMethod method; + private final List invokes = new ArrayList<>(); + + + Routes(HttpRequest request) { + Path p = new Path(request.getUrl()); + this.method = request.getHttpMethod(); + this.path = p.baseUrl(); + + } + + public Routes(HttpRequest request, Invocation defaultResponse) { + Path p = new Path(request.getUrl()); + this.method = request.getHttpMethod(); + this.path = p.baseUrl(); + if(defaultResponse != null){ + invokes.add(defaultResponse); + } else { + invokes.add(new Invocation(this, request)); + } + } + + public Routes(HttpMethod method, Path p) { + this.method = method; + this.path = p.baseUrl(); + } + + Expectation newExpectation() { + Invocation inv = new Invocation(this); + invokes.add(inv); + return inv; + } + + boolean matches(HttpRequest request) { + Path p = new Path(request.getUrl()); + return this.method.equals(request.getHttpMethod()) + && (this.path == null || this.path.equalsIgnoreCase(p.baseUrl())); + } + + RawResponse exchange(HttpRequest request, Config config) { + return getBestMatch(request) + .map(invocation -> invocation.getResponse(config, request)) + .orElseGet(() -> { + Invocation i = new Invocation(this); + i.log(request); + invokes.add(i); + return i.getResponse(config, request); + }); + } + + boolean matches(HttpMethod httpMethod, Path url) { + return this.method.equals(httpMethod) + && this.path.equals(url.baseUrl()); + } + + private Optional getBestMatch(HttpRequest request) { + Optional i = getBestMatch(request, true); + if(i.isPresent()){ + return i; + } + return getBestMatch(request, false); + } + + private Optional getBestMatch(HttpRequest request, boolean expected) { + Invocation best = null; + int bestScore = -1; + + for (Invocation invocation : invokes) { + if (!invocation.isExpected().equals(expected)) { + continue; + } + + int score = invocation.scoreMatch(request); + + if (score < 0) { + continue; + } + + if (score > bestScore) { + best = invocation; + bestScore = score; + } else if (score == bestScore + && expected + && best != null + && best.requestSize() > 0 + && invocation.requestSize() == 0) { + best = invocation; + } + } + + if (best == null) { + return Optional.empty(); + } + + best.log(request); + return Optional.of(best); + } + + @Override + public Assert hadHeader(String key, String value) { + if (invokes.stream().noneMatch(i -> i.hasExpectedHeader(key, value))) { + throw new UnirestAssertion( + "No invocation found with header [%s: %s]\nFound:\n%s", + key, value, allHeaders()); + } + return this; + } + + @Override + public Assert hadBody(String expected) { + if(invokes.stream().noneMatch(i -> i.hasBody(expected))){ + throw new UnirestAssertion( + "No invocation found with body"); + } + return this; + } + + @Override + public Assert hadField(String name, String value) { + if(invokes.stream().noneMatch(i -> i.hasField(name, value))){ + throw new UnirestAssertion( + "No invocation found with body"); + } + return this; + } + + private Headers allHeaders() { + return invokes.stream() + .flatMap(i -> i.getRequests().stream()) + .map(HttpRequest::getHeaders) + .reduce((l, r) -> { + l.putAll(r); + return l; + }).orElseGet(Headers::new); + } + + @Override + public Assert wasInvokedTimes(int i) { + Integer sum = sumInvokes(); + if (sum != i) { + throw new UnirestAssertion( + "Incorrect number of invocations. Expected %s got %s\n%s %s", + i, sum, method, path); + } + return this; + } + + private Integer sumInvokes() { + return invokes.stream() + .map(Invocation::requestSize) + .reduce(0, Integer::sum); + } + + @Override + public Assert verifyAll() { + invokes.forEach(Invocation::verify); + return this; + } + + + HttpMethod getMethod() { + return method; + } + + String getPath() { + return path; + } + + void addInvoke(Invocation invocation) { + if(invocation != null){ + invokes.add(new Invocation(this, invocation)); + } + } +} diff --git a/unirest-modules-mocks/src/main/java/kong/unirest/core/SocketSet.java b/unirest-modules-mocks/src/main/java/kong/unirest/core/SocketSet.java new file mode 100644 index 000000000..f048fe2f6 --- /dev/null +++ b/unirest-modules-mocks/src/main/java/kong/unirest/core/SocketSet.java @@ -0,0 +1,113 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.net.http.WebSocket; + +/** + * Represents a pairing of a WebSocket and its associated listener for one side of a WebSocket communication. + *

+ * A WebSocket connection involves two endpoints, each with its own socket and listener. + * This class encapsulates one side of that communication, bundling together the socket + * instance, its listener, and a descriptive name for identification purposes. + *

+ *

+ * This is primarily used in the mocking framework to simulate WebSocket connections + * for testing purposes. + *

+ * + * @param the type of WebSocket, must extend {@link WebSocket} + * @param the type of WebSocket listener, must extend {@link WebSocket.Listener} + * @see WebSocket + * @see WebSocket.Listener + */ +public class SocketSet { + + private final S socket; + private final L listener; + private final String name; + + /** + * Creates a new SocketSet with the specified socket, listener, and name. + * + * @param socket the WebSocket instance for this side of the connection + * @param listener the listener that will receive WebSocket events + * @param name a descriptive name for this socket set, used for identification and in {@link #toString()} + */ + public SocketSet(S socket, L listener, String name){ + this.socket = socket; + this.listener = listener; + this.name = name; + } + + /** + * Returns the WebSocket instance for this socket set. + * + * @return the WebSocket instance + */ + public S getSocket() { + return socket; + } + + /** + * Returns the WebSocket listener for this socket set. + * + * @return the WebSocket listener + */ + public L getListener() { + return listener; + } + + /** + * Returns the descriptive name of this socket set. + * + * @return the name of this socket set + */ + public String getName() { + return name; + } + + /** + * Returns a string representation of this socket set. + * + * @return the name of this socket set + */ + @Override + public String toString() { + return name; + } + + /** + * Simulates opening the WebSocket connection by invoking the listener's {@code onOpen} callback. + *

+ * This method triggers the {@link WebSocket.Listener#onOpen(WebSocket)} method on the + * associated listener, passing this socket set's WebSocket instance. + *

+ */ + public void open() { + listener.onOpen(socket); + } +} diff --git a/unirest-modules-mocks/src/main/java/kong/unirest/core/Times.java b/unirest-modules-mocks/src/main/java/kong/unirest/core/Times.java new file mode 100644 index 000000000..7d5a7b57c --- /dev/null +++ b/unirest-modules-mocks/src/main/java/kong/unirest/core/Times.java @@ -0,0 +1,258 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.util.Objects; + +/** + * Represents invocation count expectations for mock verification. + *

+ * This abstract class provides factory methods to create various invocation count matchers + * that can be used to verify how many times a mocked HTTP request was invoked. + *

+ * + *

Usage Examples:

+ *
{@code
+ * // Verify a request was called exactly once
+ * mockClient.verify(HttpMethod.GET, "/api/users", Times.exactlyOnce());
+ *
+ * // Verify a request was called exactly 3 times
+ * mockClient.verify(HttpMethod.POST, "/api/data", Times.exactly(3));
+ *
+ * // Verify a request was called at least once
+ * mockClient.verify(HttpMethod.GET, "/api/health", Times.atLeastOnce());
+ *
+ * // Verify a request was never called
+ * mockClient.verify(HttpMethod.DELETE, "/api/forbidden", Times.never());
+ *
+ * // Verify a request was called at most 5 times
+ * mockClient.verify(HttpMethod.GET, "/api/resource", Times.atMost(5));
+ * }
+ * + * @see EvaluationResult + */ +public abstract class Times { + + /** + * Creates an expectation that the invocation occurred exactly once. + *

+ * This is a convenience method equivalent to {@code exactly(1)}. + *

+ * + * @return a Times instance expecting exactly one invocation + */ + public static Times exactlyOnce() { + return exactly(1); + } + + /** + * Creates an expectation that the invocation occurred exactly the specified number of times. + * + * @param times the exact number of expected invocations + * @return a Times instance expecting exactly the specified number of invocations + */ + public static Times exactly(int times) { + return new Exactly(times); + } + + /** + * Creates an expectation that the invocation occurred at least once. + *

+ * This is a convenience method equivalent to {@code atLeast(1)}. + *

+ * + * @return a Times instance expecting at least one invocation + */ + public static Times atLeastOnce() { + return atLeast(1); + } + + /** + * Creates an expectation that the invocation occurred at least the specified number of times. + * + * @param times the minimum number of expected invocations + * @return a Times instance expecting at least the specified number of invocations + */ + public static Times atLeast(int times) { + return new AtLeast(times); + } + + /** + * Creates an expectation that the invocation never occurred. + *

+ * This is a convenience method equivalent to {@code exactly(0)}. + *

+ * + * @return a Times instance expecting zero invocations + */ + public static Times never() { + return exactly(0); + } + + /** + * Creates an expectation that the invocation occurred at most the specified number of times. + * + * @param times the maximum number of expected invocations + * @return a Times instance expecting at most the specified number of invocations + */ + public static Times atMost(int times) { + return new AtMost(times); + } + + /** + * Evaluates whether the actual invocation count matches this expectation. + * + * @param number the actual number of invocations + * @return an {@link EvaluationResult} indicating success or failure with a descriptive message + */ + public abstract EvaluationResult matches(int number); + + /** + * Times implementation that expects exactly a specific number of invocations. + */ + private static class Exactly extends Times { + private final int times; + Exactly(int times) { + this.times = times; + } + + @Override + public EvaluationResult matches(int number) { + if(Objects.equals(number, times)){ + return EvaluationResult.success(); + } else { + return EvaluationResult.fail("Expected exactly %s invocations but got %s", times, number); + } + } + } + + /** + * Times implementation that expects at least a specific number of invocations. + */ + private static class AtLeast extends Times { + private final int times; + AtLeast(int times) { + this.times = times; + } + + @Override + public EvaluationResult matches(int number) { + if(number >= times) { + return EvaluationResult.success(); + } else { + return EvaluationResult.fail("Expected at least %s invocations but got %s", times, number); + } + } + } + + /** + * Times implementation that expects at most a specific number of invocations. + */ + private static class AtMost extends Times { + private final int times; + + public AtMost(int times) { + this.times = times; + } + + @Override + public EvaluationResult matches(int number) { + if (number <= times){ + return EvaluationResult.success(); + } else { + return EvaluationResult.fail("Expected no more than %s invocations but got %s", times, number); + } + } + } + + /** + * Represents the result of evaluating an invocation count against a {@link Times} expectation. + *

+ * An evaluation result contains a success flag and, in case of failure, a descriptive message + * explaining why the expectation was not met. + *

+ */ + public static class EvaluationResult { + private static final EvaluationResult SUCCESS = new EvaluationResult(true, null); + private final boolean success; + private final String message; + + /** + * Returns a successful evaluation result. + *

+ * This method returns a cached singleton instance for efficiency. + *

+ * + * @return a successful EvaluationResult + */ + public static EvaluationResult success(){ + return SUCCESS; + } + + /** + * Creates a failed evaluation result with a formatted message. + * + * @param message the message format string + * @param values the values to substitute into the message format + * @return a failed EvaluationResult with the formatted message + */ + public static EvaluationResult fail(String message, Object... values){ + return new EvaluationResult(false, String.format(message, values)); + } + + /** + * Constructs a new EvaluationResult with the specified success status and message. + * + * @param success {@code true} if the evaluation succeeded, {@code false} otherwise + * @param message the message describing the result, typically {@code null} for success + * and a descriptive error message for failure + */ + public EvaluationResult(boolean success, String message){ + this.success = success; + this.message = message; + } + + /** + * Returns whether the evaluation was successful. + * + * @return {@code true} if the invocation count matched the expectation, {@code false} otherwise + */ + public boolean isSuccess() { + return success; + } + + /** + * Returns the message associated with this evaluation result. + * + * @return the message, or {@code null} if the evaluation was successful + */ + public String getMessage() { + return message; + } + } + + +} diff --git a/unirest-modules-mocks/src/main/java/kong/unirest/core/UnirestAssertion.java b/unirest-modules-mocks/src/main/java/kong/unirest/core/UnirestAssertion.java new file mode 100644 index 000000000..aec2f2075 --- /dev/null +++ b/unirest-modules-mocks/src/main/java/kong/unirest/core/UnirestAssertion.java @@ -0,0 +1,64 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +/** + * Thrown to indicate that a mock verification or assertion has failed. + *

+ * This exception extends {@link AssertionError} and is used by the Unirest mock framework + * to signal that an expected condition was not met. It is thrown by various assertion + * and verification methods in the mocking API. + *

+ * + *

Common scenarios where this is thrown:

+ *
    + *
  • An expected request was never invoked
  • + *
  • A request was invoked a different number of times than expected
  • + *
  • An expected message was not received on a WebSocket
  • + *
  • A mock component was not properly initialized
  • + *
+ * + * @see MockClient#assertThat(HttpMethod, String) + * @see MockClient#verifyAll() + * @see MockListener + * @see Times + */ +public class UnirestAssertion extends AssertionError { + + /** + * Constructs a new UnirestAssertion with a formatted message. + *

+ * The message is formatted using {@link String#format(String, Object...)}, + * allowing for dynamic error messages with placeholders. + *

+ * + * @param base the format string for the error message + * @param args the arguments to be substituted into the format string + */ + public UnirestAssertion(String base, Object... args){ + super(String.format(base, args)); + } +} diff --git a/unirest-modules-mocks/src/test/java/kong/tests/AssertTest.java b/unirest-modules-mocks/src/test/java/kong/tests/AssertTest.java new file mode 100644 index 000000000..d4f0f431c --- /dev/null +++ b/unirest-modules-mocks/src/test/java/kong/tests/AssertTest.java @@ -0,0 +1,283 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.tests; + +import kong.unirest.core.*; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.function.Supplier; + +import static kong.unirest.core.HttpMethod.GET; +import static kong.unirest.core.HttpMethod.POST; +import static org.junit.jupiter.api.Assertions.*; + +class AssertTest extends Base { + + @Test + void canAssertANumberOfTimes() { + Unirest.get(path).asEmpty(); + Unirest.get(path).asEmpty(); + + Assert exp = client.assertThat(HttpMethod.GET, path); + exp.wasInvokedTimes(2); + + assertException(() -> exp.wasInvokedTimes(1), + "Incorrect number of invocations. Expected 1 got 2\n" + + "GET http://basic"); + + assertException(() -> exp.wasInvokedTimes(3), + "Incorrect number of invocations. Expected 3 got 2\n" + + "GET http://basic"); + } + + @Test + void noExpectationsAtAll() { + Unirest.get(path).asEmpty(); + client.verifyAll(); + + } + + @Test + void noExpectation() { + client.expect(GET, otherPath); + assertException(() -> client.verifyAll(), + "Expected at least 1 invocations but got 0\n" + + "GET http://other\n"); + } + + @Test + void noInvocationHappened() { + assertException(() -> client.assertThat(GET, path), + "No Matching Invocation:: GET http://basic"); + } + + @Test + void assertHeader() { + Unirest.get(path).header("monster", "grover").asEmpty(); + + Assert expect = client.assertThat(GET, path); + expect.hadHeader("monster", "grover"); + + assertException(() -> expect.hadHeader("monster", "oscar"), + "No invocation found with header [monster: oscar]\nFound:\nmonster: grover"); + } + + @Test + void canSetHeaderExpectationOnExpects() { + client.expect(GET, path).header("monster", "grover"); + + assertException(() -> client.verifyAll(), + "Expected at least 1 invocations but got 0\n" + + "GET http://basic\n" + + "Headers:\n" + + "monster: grover"); + + Unirest.get(path).header("monster", "grover").asEmpty(); + + client.verifyAll(); + } + + @Test + public void expectAnyPath(){ + client.expect(HttpMethod.GET) + .thenReturn("woh"); + + Unirest.get(path).asEmpty(); + + client.verifyAll(); + } + + @Test + void canExpectQueryParams() { + client.expect(GET, path).queryString("monster", "grover"); + + Unirest.get(path).asEmpty(); + + assertException(() -> client.verifyAll()); + + Unirest.get(path).queryString("monster", "grover").asEmpty(); + + client.verifyAll(); + } + + @Test + void expectBody() { + client.expect(POST, path) + .body("foo") + .thenReturn("bar"); + + assertNull(Unirest.post(path).asString().getBody()); + assertEquals("bar", Unirest.post(path).body("foo").asString().getBody()); + } + + @Test + void expectBodyFailure() { + client.expect(POST, path) + .body("foo") + .thenReturn("bar"); + + Unirest.post(path).body("baz").asString(); + + assertException(() -> client.verifyAll(), + "Expected at least 1 invocations but got 0\n" + + "POST http://basic\n" + + "Body:\n" + + "\tfoo"); + } + + @Test + void assertBody() { + client.expect(POST, path); + + Unirest.post(path).body("hey buddy").asString(); + + client.assertThat(POST, path).hadBody("hey buddy"); + } + + @Test + void assertBody_fail() { + client.expect(POST, path); + + Unirest.post(path).body("hey buddy").asString(); + + assertThrows(UnirestAssertion.class, + () -> client.assertThat(POST, path).hadBody("I'm a big ol beat")); + } + + @Test + void assertBody_multipart() { + client.expect(POST, path); + + Unirest.post(path).field("hey", "buddy").asString(); + + client.assertThat(POST, path).hadField("hey", "buddy"); + } + + @Test + void assertBody_multipart_fails() { + client.expect(POST, path); + + Unirest.post(path).field("hey", "buddy").asString(); + assertThrows(UnirestAssertion.class, () -> client.assertThat(POST, path).hadField("nope", "buddy")); + assertThrows(UnirestAssertion.class, () -> client.assertThat(POST, path).hadField("hey", "nope")); + } + + @Test + void canResetExpects() { + client.expect(GET).thenReturn("HI"); + client.reset(); + assertEquals(null, Unirest.get(path).asString().getBody()); + } + + @Test + void canBeStrictAndForbidAnythingWithoutAMatch() { + client.defaultResponse().withStatus(400, "wtf") + .thenReturn("boo"); + + client.expect(GET, otherPath).thenReturn("Hi"); + + HttpResponse res = Unirest.get(path).asString(); + assertEquals(400, res.getStatus()); + assertEquals("wtf", res.getStatusText()); + assertEquals("boo", res.getBody()); + + assertEquals("Hi", Unirest.get(otherPath).asString().getBody()); + } + + @Test + void canSetASupplierForTheReponseBody() { + BodyBuddy supplier = new BodyBuddy(); + client.expect(GET).thenReturn(supplier); + supplier.body = "Hey Buddy"; + assertEquals("Hey Buddy", Unirest.get(path).asString().getBody()); + supplier.body = "Yeaaaah Buddy"; + assertEquals("Yeaaaah Buddy", Unirest.get(path).asString().getBody()); + } + + @Test + void assertPostWithNoBody() { + client.expect(HttpMethod.POST, "myurl").body("test").thenReturn().withStatus(200); + UnirestAssertion ex = assertThrows(UnirestAssertion.class, () -> client.verifyAll()); + assertEquals("Expected at least 1 invocations but got 0\n" + + "POST myurl\n" + + "Body:\n" + + "\t null", ex.getMessage()); + } + + @Test + void returnAMockResponseObject() { + client.expect(HttpMethod.POST, path) + .thenReturn(MockResponse.of(500, "a 500 brah", "error") + .withHeader("cool", "beans")); + + HttpResponse response = Unirest.post(path).asString(); + + assertEquals(500, response.getStatus()); + assertEquals("a 500 brah", response.getStatusText()); + assertEquals("error", response.getBody()); + assertEquals(List.of("beans"), response.getHeaders().get("cool")); + } + + @Test + void returnAMockResponseObjectWithoutStuff() { + client.expect(HttpMethod.POST, path) + .thenReturn(MockResponse.of(500, null)); + + HttpResponse response = Unirest.post(path).asString(); + + assertEquals(500, response.getStatus()); + assertNull(response.getBody()); + assertEquals(List.of(), response.getHeaders().get("cool")); + } + + @Test + void verbsAreImportant() { + client.expect(GET, path).thenReturn("hi"); + assertNotEquals("hi", Unirest.post(path).asString().getBody()); + } + + @Test + public void samePathDifferentMethodTest() { + client.expect(HttpMethod.GET, path); + client.expect(HttpMethod.POST, path); + + Unirest.get(path).asJson(); + client.assertThat(HttpMethod.GET, path).wasInvokedTimes(1); + + Unirest.post(path).asJson(); + client.assertThat(HttpMethod.GET, path).wasInvokedTimes(1); + client.assertThat(HttpMethod.POST, path).wasInvokedTimes(1); + } + + private static class BodyBuddy implements Supplier{ + String body; + @Override + public String get() { + return body; + } + } +} diff --git a/unirest-modules-mocks/src/test/java/kong/tests/AsyncTest.java b/unirest-modules-mocks/src/test/java/kong/tests/AsyncTest.java new file mode 100644 index 000000000..9e541f384 --- /dev/null +++ b/unirest-modules-mocks/src/test/java/kong/tests/AsyncTest.java @@ -0,0 +1,46 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.tests; + +import kong.unirest.core.HttpMethod; +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class AsyncTest extends Base { + + @Test + void canExpectAsync() throws Exception { + client.expect(HttpMethod.GET, path).thenReturn("Hey Ma"); + + String body = Unirest.get(path).asStringAsync().get().getBody(); + + assertEquals("Hey Ma", body); + + client.verifyAll(); + } +} diff --git a/unirest-modules-mocks/src/test/java/kong/tests/Base.java b/unirest-modules-mocks/src/test/java/kong/tests/Base.java new file mode 100644 index 000000000..e4717f229 --- /dev/null +++ b/unirest-modules-mocks/src/test/java/kong/tests/Base.java @@ -0,0 +1,74 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.tests; + +import kong.unirest.core.MockClient; +import kong.unirest.core.Unirest; +import kong.unirest.core.UnirestAssertion; +import kong.unirest.modules.gson.GsonObjectMapper; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +public class Base { + protected MockClient client; + protected String path = "http://basic";; + protected String otherPath = "http://other";; + + @BeforeEach + public void setUp() { + client = MockClient.register(); + Unirest.config().setObjectMapper(new GsonObjectMapper()); + } + + @AfterEach + void tearDown() { + MockClient.clear(); + } + + public static void assertException(ExRunnable runnable, String message) { + try{ + runnable.run(); + fail("Expected exception but got none."); + } catch (UnirestAssertion e){ + assertEquals(message, e.getMessage(), "Wrong Error Message"); + } + } + + public static void assertException(ExRunnable runnable) { + try{ + runnable.run(); + fail("Expected exception but got none."); + } catch (UnirestAssertion ignored){ } + } + + @FunctionalInterface + public interface ExRunnable { + void run() throws Error; + } +} diff --git a/unirest-modules-mocks/src/test/java/kong/tests/BodyMatchingTest.java b/unirest-modules-mocks/src/test/java/kong/tests/BodyMatchingTest.java new file mode 100644 index 000000000..69fa83bf5 --- /dev/null +++ b/unirest-modules-mocks/src/test/java/kong/tests/BodyMatchingTest.java @@ -0,0 +1,88 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.tests; + +import kong.unirest.core.HttpMethod; +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; + +import static kong.unirest.core.FieldMatcher.of; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class BodyMatchingTest extends Base { + + @Test + void canTestForForms() { + client.expect(HttpMethod.POST, path) + .body(of("beetle", "ringo", "number", "42")) + .thenReturn() + .withStatus(200); + + Unirest.post(path) + .field("beetle", "ringo") + .field("number", "42") + .asEmpty(); + + client.verifyAll(); + } + + @Test + void missingAField() { + client.expect(HttpMethod.POST, path) + .body(of("beetle", "ringo", "number", "42")) + .thenReturn() + .withStatus(200); + + Unirest.post(path) + .field("beetle", "ringo") + .asEmpty(); + + assertException(() -> client.verifyAll(), + "Expected at least 1 invocations but got 0\n" + + "POST http://basic\n" + + "Body:\n" + + "\tnumber=42"); + } + + @Test + void forFieldsShouldBeEncoded() { + client.expect(HttpMethod.POST, path) + .body(of("beetles", "ringo george", + "url", "http://somewhere")) + .thenReturn("cool") + .withStatus(200); + + String result = Unirest.post(path) + .field("beetles", "ringo george") + .field("url", "http://somewhere") + .asString() + .getBody(); + + client.verifyAll(); + + assertEquals("cool", result); + } +} diff --git a/unirest-modules-mocks/src/test/java/kong/tests/DefultResponseTest.java b/unirest-modules-mocks/src/test/java/kong/tests/DefultResponseTest.java new file mode 100644 index 000000000..dee2fb8d9 --- /dev/null +++ b/unirest-modules-mocks/src/test/java/kong/tests/DefultResponseTest.java @@ -0,0 +1,48 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.tests; + +import kong.unirest.core.*; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +public class DefultResponseTest { + + @Test + void defaultResponseWorksWithHeaders(){ + var i = Unirest.spawnInstance(); + var mock = MockClient.register(i); + + mock.defaultResponse().withStatus(500); + + var response = i.get("http://fake-url") + .header("header1", "value1") + .asEmpty(); + + assertThat(response.getStatus()).isEqualTo(500); + } +} diff --git a/unirest-modules-mocks/src/test/java/kong/tests/DocumentationExamplesTest.java b/unirest-modules-mocks/src/test/java/kong/tests/DocumentationExamplesTest.java new file mode 100644 index 000000000..503e9d434 --- /dev/null +++ b/unirest-modules-mocks/src/test/java/kong/tests/DocumentationExamplesTest.java @@ -0,0 +1,187 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.tests; + +import kong.unirest.core.*; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class DocumentationExamplesTest { + @AfterEach + public void tearDown(){ + MockClient.clear(); + } + + @Test + void mockStatic(){ + MockClient mock = MockClient.register(); + + mock.expect(HttpMethod.GET, "http://zombo.com") + .thenReturn("You can do anything!"); + + assertEquals( + "You can do anything!", + Unirest.get("http://zombo.com").asString().getBody() + ); + + //Optional: Verify all expectations were fulfilled + mock.verifyAll(); + } + + @Test + void mockInstant(){ + UnirestInstance unirest = Unirest.spawnInstance(); + MockClient mock = MockClient.register(unirest); + + mock.expect(HttpMethod.GET, "http://zombo.com") + .thenReturn("You can do anything!"); + + assertEquals( + "You can do anything!", + unirest.get("http://zombo.com").asString().getBody() + ); + + //Optional: Verify all expectations were fulfilled + mock.verifyAll(); + } + + @Test + void multipleExpects(){ + MockClient mock = MockClient.register(); + + mock.expect(HttpMethod.POST, "https://somewhere.bad") + .thenReturn("I'm Bad"); + + mock.expect(HttpMethod.GET, "http://zombo.com") + .thenReturn("You can do anything!"); + + mock.expect(HttpMethod.GET, "http://zombo.com") + .header("foo", "bar") + .thenReturn("You can do anything with headers!"); + + assertEquals( + "You can do anything with headers!", + Unirest.get("http://zombo.com") + .header("foo", "bar") + .asString().getBody() + ); + + assertEquals( + "You can do anything!", + Unirest.get("http://zombo.com") + .asString().getBody() + ); + } + + @Test + void validate(){ + MockClient mock = MockClient.register(); + + mock.expect(HttpMethod.POST, "http://zombo.com") + .thenReturn().withStatus(200); + + Unirest.post("http://zombo.com").asString().getBody(); + + mock.verifyAll(); + } + + @Test + void multipleValidates(){ + MockClient mock = MockClient.register(); + + var zombo = mock.expect(HttpMethod.POST, "http://zombo.com").thenReturn(); + var homestar = mock.expect(HttpMethod.DELETE, "http://homestarrunner.com").thenReturn(); + + Unirest.post("http://zombo.com").asString().getBody(); + + zombo.verify(); + homestar.verify(Times.never()); + } + + @Test + void simpleBody() { + MockClient mock = MockClient.register(); + + mock.expect(HttpMethod.POST, "http://zombo.com") + .body("I can do anything? Anything at all?") + .thenReturn() + .withStatus(201); + + assertEquals(201, + Unirest.post("http://zombo.com").body("I can do anything? Anything at all?").asEmpty().getStatus() + ); + } + + @Test + void formParams() { + MockClient mock = MockClient.register(); + + mock.expect(HttpMethod.POST, "http://zombo.com") + .body(FieldMatcher.of("foo", "bar", + "baz", "qux")) + .thenReturn() + .withStatus(201); + + assertEquals(201, + Unirest.post("http://zombo.com") + .field("foo", "bar") + .field("baz", "qux") + .asEmpty().getStatus() + ); + } + + @Test + void response() { + MockClient mock = MockClient.register(); + + mock.expect(HttpMethod.GET, "http://zombo.com") + .thenReturn("Care for some tea mum?") + .withHeader("x-zombo-brewing", "active") + .withStatus(418, "I am a teapot"); + + var response = Unirest.get("http://zombo.com").asString(); + + assertEquals(418, response.getStatus()); + assertEquals("I am a teapot", response.getStatusText()); + assertEquals("Care for some tea mum?", response.getBody()); + assertEquals("active", response.getHeaders().getFirst("x-zombo-brewing")); + } + + static class Teapot { public String brewstatus = "on"; } + @Test + void pojos() { + MockClient mock = MockClient.register(); + + mock.expect(HttpMethod.GET, "http://zombo.com") + .thenReturn(new Teapot()); + + var response = Unirest.get("http://zombo.com").asString(); + + assertEquals("{\"brewstatus\":\"on\"}", response.getBody()); + } +} diff --git a/unirest-modules-mocks/src/test/java/kong/tests/ErrorTest.java b/unirest-modules-mocks/src/test/java/kong/tests/ErrorTest.java new file mode 100644 index 000000000..c364e0378 --- /dev/null +++ b/unirest-modules-mocks/src/test/java/kong/tests/ErrorTest.java @@ -0,0 +1,55 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.tests; + +import kong.unirest.core.HttpMethod; +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; + +import static kong.unirest.core.HttpStatus.INTERNAL_SERVER_ERROR; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ErrorTest extends Base { + + @Test + void mapError() { + client.expect(HttpMethod.GET, path) + .thenReturn(new ErrorObj("Boo!")) + .withStatus(INTERNAL_SERVER_ERROR); + + ErrorObj err = Unirest.get(path).asJson().mapError(ErrorObj.class); + + assertEquals("Boo!", err.say); + } + + private static class ErrorObj { + public String say; + + public ErrorObj(String say) { + this.say = say; + } + } +} diff --git a/unirest-modules-mocks/src/test/java/kong/tests/ExpectedResponseTest.java b/unirest-modules-mocks/src/test/java/kong/tests/ExpectedResponseTest.java new file mode 100644 index 000000000..ca6624fa0 --- /dev/null +++ b/unirest-modules-mocks/src/test/java/kong/tests/ExpectedResponseTest.java @@ -0,0 +1,179 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.tests; + +import kong.unirest.core.*; +import kong.unirest.core.json.JSONObject; +import org.junit.jupiter.api.Test; + +import static kong.unirest.core.HttpMethod.GET; +import static kong.unirest.core.HttpStatus.BAD_REQUEST; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ExpectedResponseTest extends Base { + + @Test + void getRequestSummaryOnResponse() { + client.expect(GET, "http://localhost/get/cheese/passed") + .queryString("params", "cheese") + .thenReturn(); + + HttpRequestSummary sum = Unirest.get("http://localhost/get/{params}/passed") + .routeParam("params", "cheese") + .queryString("fruit", "apples") + .asEmpty() + .getRequestSummary(); + + assertEquals("http://localhost/get/cheese/passed?fruit=apples", sum.getUrl()); + assertEquals("http://localhost/get/{params}/passed", sum.getRawPath()); + assertEquals(HttpMethod.GET, sum.getHttpMethod()); + } + + @Test + void canExpectErrors() { + client.expect(HttpMethod.GET, path).thenReturn().withStatus(BAD_REQUEST, "oh noes"); + + HttpResponse response = Unirest.get(path).asString(); + + assertEquals(BAD_REQUEST, response.getStatus()); + assertEquals("oh noes", response.getStatusText()); + } + + @Test + void canExpectErrorsJustStatus() { + client.expect(HttpMethod.GET, path).thenReturn().withStatus(BAD_REQUEST); + + HttpResponse response = Unirest.get(path).asString(); + + assertEquals(BAD_REQUEST, response.getStatus()); + assertEquals("", response.getStatusText()); + } + + @Test + void simpleGetString() { + client.expect(HttpMethod.GET, path) + .thenReturn("Hello World"); + + assertEquals("Hello World", Unirest.get(path).asString().getBody()); + } + + @Test + void simpleGetBytes() { + client.expect(HttpMethod.GET, path) + .thenReturn("Hello World"); + + byte[] body = Unirest.get(path).asBytes().getBody(); + assertEquals("Hello World", new String(body)); + } + + @Test + void simpleJson() { + client.expect(HttpMethod.GET, path) + .thenReturn("{\"fruit\": \"apple\"}"); + + assertEquals("apple", + Unirest.get(path).asJson().getBody().getObject().getString("fruit")); + } + + @Test + void setReturnAsJson() { + client.expect(HttpMethod.GET, path) + .thenReturn(new JSONObject("{\"fruit\": \"apple\"}")); + + assertEquals("apple", + Unirest.get(path).asJson().getBody().getObject().getString("fruit")); + } + + @Test + void canPassInAndReturnObjectsAsJson() { + client.expect(HttpMethod.GET, path) + .thenReturn(new Pojo("apple")); + + Pojo pojo = Unirest.get(path).asObject(Pojo.class).getBody(); + assertEquals("apple", pojo.fruit); + } + + @Test + void willUseRequestObjectMapperIfSupplied() { + client.expect(HttpMethod.GET, path) + .thenReturn(new Pojo("apple")); + + String pojo = Unirest.get(path) + .withObjectMapper(new DerpMapper()) + .asString() + .getBody(); + + assertEquals("derp", pojo); + } + + @Test + void willUseConfigObjectMapperIfSupplied() { + client.expect(HttpMethod.GET, path) + .thenReturn(new Pojo("apple")); + + Unirest.config().setObjectMapper(new DerpMapper()); + + String pojo = Unirest.get(path) + .asString() + .getBody(); + + assertEquals("derp", pojo); + } + + @Test + void canSetResponseHeaders() { + client.expect(GET, path) + .thenReturn("foo") + .withHeader("monster", "grover"); + + HttpResponse rez = Unirest.get(path).asString(); + assertEquals("foo", rez.getBody()); + assertEquals("grover", rez.getHeaders().getFirst("monster")); + } + + @Test + void canReturnEmptyWithHeaders() { + client.expect(GET, path) + .thenReturn() + .withHeader("monster", "grover"); + + HttpResponse rez = Unirest.get(path).asString(); + assertEquals(null, rez.getBody()); + assertEquals("grover", rez.getHeaders().getFirst("monster")); + } + + private class DerpMapper implements ObjectMapper { + @Override + public T readValue(String value, Class valueType) { + return (T)value; + } + + @Override + public String writeValue(Object value) { + return "derp"; + } + } +} diff --git a/unirest-modules-mocks/src/test/java/kong/tests/MetricsTest.java b/unirest-modules-mocks/src/test/java/kong/tests/MetricsTest.java new file mode 100644 index 000000000..3fb67bfc8 --- /dev/null +++ b/unirest-modules-mocks/src/test/java/kong/tests/MetricsTest.java @@ -0,0 +1,54 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.tests; + +import kong.unirest.core.HttpMethod; +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class MetricsTest extends Base { + + boolean wasCalled = false; + + @Test + void willExecuteMetrics() { + Unirest.config().instrumentWith( + r -> (re, ex) -> { + wasCalled = true; + } + ); + + client.expect(HttpMethod.GET, "http://foo").thenReturn(); + + Unirest.get("http://foo").asEmpty(); + + assertTrue(wasCalled); + } + + +} diff --git a/unirest-modules-mocks/src/test/java/kong/tests/MockClientInterceptorIssueTest.java b/unirest-modules-mocks/src/test/java/kong/tests/MockClientInterceptorIssueTest.java new file mode 100644 index 000000000..03863acdb --- /dev/null +++ b/unirest-modules-mocks/src/test/java/kong/tests/MockClientInterceptorIssueTest.java @@ -0,0 +1,77 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.tests; + +import kong.unirest.core.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; + +class MockClientInterceptorIssueTest { + + private final ExampleInterceptor interceptor = new ExampleInterceptor(); + + private static class ExampleInterceptor implements Interceptor { + + public HttpResponse response; + + @Override + public void onRequest(HttpRequest request, Config config) { + request.header("Test-Header", "Header Value"); + } + + @Override + public void onResponse(HttpResponse response, HttpRequestSummary request, Config config) { + this.response = response; + } + } + + private UnirestInstance unirestInstance; + + @BeforeEach + public void setup() { + this.unirestInstance = Unirest.spawnInstance(); + this.unirestInstance.config().interceptor(interceptor); + } + + @Test + void interceptor_is_called() { + MockClient unirestMock = MockClient.register(this.unirestInstance); + + unirestMock + .expect(HttpMethod.GET, "http://example.com") + .header("Test-Header", "Header Value") + .thenReturn("Call Result"); + + HttpResponse response = this.unirestInstance.get("http://example.com").asString(); + + unirestMock.verifyAll(); + assertEquals("Call Result", response.getBody()); + assertSame(response, interceptor.response); + } +} diff --git a/unirest-modules-mocks/src/test/java/kong/tests/MockWebSocketTest.java b/unirest-modules-mocks/src/test/java/kong/tests/MockWebSocketTest.java new file mode 100644 index 000000000..838e58b68 --- /dev/null +++ b/unirest-modules-mocks/src/test/java/kong/tests/MockWebSocketTest.java @@ -0,0 +1,83 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.tests; + + +import kong.unirest.core.MockWebSocket; +import kong.unirest.core.SocketSet; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.net.http.WebSocket; +import java.nio.ByteBuffer; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +class MockWebSocketTest { + ByteBuffer buff = ByteBuffer.wrap("Hail Sithis!".getBytes()); + WebSocket remoteSocket; + WebSocket.Listener remoteListener; + MockWebSocket testSocket; + + @BeforeEach + void setUp() { + remoteSocket = mock(WebSocket.class); + remoteListener = mock(WebSocket.Listener.class); + testSocket = new MockWebSocket(); + testSocket.init(new SocketSet(remoteSocket, remoteListener, "test")); + } + + @Test + void sendText() { + testSocket.sendText("Hail Sithis!", false); + verify(remoteListener).onText(remoteSocket, "Hail Sithis!", false); + } + + @Test + void sendBinary() { + testSocket.sendBinary(buff, false); + verify(remoteListener).onBinary(remoteSocket, buff, false); + } + + @Test + void sendPing() { + testSocket.sendPing(buff); + verify(remoteListener).onPing(remoteSocket, buff); + } + + @Test + void sendPong() { + testSocket.sendPong(buff); + verify(remoteListener).onPong(remoteSocket, buff); + } + + @Test + void sendClose() { + testSocket.sendClose(418, "I am a Teapot"); + verify(remoteListener).onClose(remoteSocket, 418, "I am a Teapot"); + } +} diff --git a/unirest-modules-mocks/src/test/java/kong/tests/MultipleExpectsTest.java b/unirest-modules-mocks/src/test/java/kong/tests/MultipleExpectsTest.java new file mode 100644 index 000000000..651fa2eef --- /dev/null +++ b/unirest-modules-mocks/src/test/java/kong/tests/MultipleExpectsTest.java @@ -0,0 +1,121 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.tests; + +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; + +import static kong.unirest.core.HttpMethod.GET; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class MultipleExpectsTest extends Base { + + @Test + void canDifferenciateBetweenExpectations() { + client.expect(GET, path).header("monster", "oscar").header("fruit", "apple"); + client.expect(GET, path).header("monster", "oscar"); + + Unirest.get(path).header("monster", "oscar").asEmpty(); + + assertException(() -> client.verifyAll(), + "Expected at least 1 invocations but got 0\n" + + "GET http://basic\n" + + "Headers:\n" + + "monster: oscar\n" + + "fruit: apple"); + } + + @Test + void willPickTheBestMatchingExpectation() { + client.expect(GET, path) + .header("monster", "grover") + .header("fruit", "apples"); + + client.expect(GET, path) + .header("monster", "grover"); + + Unirest.get(path) + .header("monster", "grover") + .header("fruit", "apples") + .asEmpty(); + + assertException(() -> client.verifyAll(), + "Expected at least 1 invocations but got 0\n" + + "GET http://basic\n" + + "Headers:\n" + + "monster: grover"); + } + + @Test + void willPickTheBestMatchingQueryExpectation() { + client.expect(GET, path) + .queryString("monster", "grover"); + + client.expect(GET, path) + .queryString("monster", "grover") + .queryString("fruit", "apples"); + + client.expect(GET, path) + .queryString("monster", "grover"); + + Unirest.get(path) + .queryString("monster", "grover") + .queryString("fruit", "apples") + .asEmpty(); + + assertException(() -> client.verifyAll(), + "Expected at least 1 invocations but got 0\n" + + "GET http://basic\n" + + "Params:\n" + + "monster: grover"); + } + + @Test + void whenDuplicateExpectsExistUseThemInOrder() { + client.expect(GET, path) + .queryString("monster", "grover") + .thenReturn("one"); + + client.expect(GET, path) + .queryString("monster", "grover") + .thenReturn("two"); + + String first = Unirest.get(path) + .queryString("monster", "grover") + .asString() + .getBody(); + + String second = Unirest.get(path) + .queryString("monster", "grover") + .asString() + .getBody(); + + assertEquals("one", first); + assertEquals("two", second); + + client.verifyAll(); + } +} diff --git a/unirest-modules-mocks/src/test/java/kong/tests/Pojo.java b/unirest-modules-mocks/src/test/java/kong/tests/Pojo.java new file mode 100644 index 000000000..c384922bb --- /dev/null +++ b/unirest-modules-mocks/src/test/java/kong/tests/Pojo.java @@ -0,0 +1,36 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.tests; + +public class Pojo { + public String fruit; + + public Pojo(){} + + public Pojo(String fruit) { + this.fruit = fruit; + } +} diff --git a/unirest-modules-mocks/src/test/java/kong/tests/RawResponseTest.java b/unirest-modules-mocks/src/test/java/kong/tests/RawResponseTest.java new file mode 100644 index 000000000..0c103f155 --- /dev/null +++ b/unirest-modules-mocks/src/test/java/kong/tests/RawResponseTest.java @@ -0,0 +1,156 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.tests; + +import com.google.common.base.Charsets; +import com.google.common.io.CharStreams; +import kong.unirest.core.HttpMethod; +import kong.unirest.core.HttpResponseSummary; +import kong.unirest.core.HttpStatus; +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class RawResponseTest extends Base { + + @Test + void getContentAsInputStream() { + client.expect(HttpMethod.GET, path).thenReturn("hi"); + + Unirest.get(path).thenConsume(raw -> { + InputStream i = raw.getContent(); + String result = readToString(i); + assertEquals("hi", result); + }); + } + + @Test + void getContentAsInputStreamReader() { + client.expect(HttpMethod.GET, path).thenReturn("hi"); + + Unirest.get(path).thenConsume(raw -> { + InputStreamReader i = raw.getContentReader(); + String result = readToString(i); + assertEquals("hi", result); + }); + } + + @Test + void getContentAsString() { + client.expect(HttpMethod.GET, path).thenReturn("hi"); + + Unirest.get(path).thenConsume(raw -> { + String i = raw.getContentAsString(); + assertEquals("hi", i); + }); + } + + @Test + void getContentAsBytes() { + client.expect(HttpMethod.GET, path).thenReturn("hi"); + + Unirest.get(path).thenConsume(raw -> { + byte[] i = raw.getContentAsBytes(); + assertEquals("hi", new String(i)); + }); + } + + @Test + void getContentAsStringWithEncoding() { + client.expect(HttpMethod.GET, path).thenReturn("hi"); + + Unirest.get(path).thenConsume(raw -> { + String i = raw.getContentAsString("UTF-8"); + assertEquals("hi", i); + }); + } + + @Test + void getContentType() { + client.expect(HttpMethod.GET, path) + .thenReturn("hi") + .withHeader("Content-Type", "text/html"); + + Unirest.get(path).thenConsume(raw -> { + String i = raw.getContentType(); + assertEquals("text/html", i); + }); + } + + @Test + void getSummary() { + client.expect(HttpMethod.GET, path) + .thenReturn("hi") + .withStatus(HttpStatus.IM_A_TEAPOT, "Tip me over and pour me out"); + + Unirest.get(path).thenConsume(raw -> { + HttpResponseSummary i = raw.toSummary(); + assertEquals(HttpStatus.IM_A_TEAPOT, i.getStatus()); + assertEquals("Tip me over and pour me out", i.getStatusText()); + }); + } + + @Test + void getStatus() { + client.expect(HttpMethod.GET, path) + .thenReturn("hi") + .withStatus(HttpStatus.IM_A_TEAPOT, "Tip me over and pour me out"); + + Unirest.get(path).thenConsume(raw -> { + assertEquals(HttpStatus.IM_A_TEAPOT, raw.getStatus()); + assertEquals("Tip me over and pour me out", raw.getStatusText()); + }); + } + + @Test + void getEncoding() { + client.expect(HttpMethod.GET, path) + .thenReturn("hi") + .withHeader("Content-Encoding", "Klingon-32"); + + Unirest.get(path).thenConsume(raw -> { + String encoding = raw.getEncoding(); + assertEquals("Klingon-32", encoding); + }); + } + + private String readToString(InputStream i) { + return readToString(new InputStreamReader(i, Charsets.UTF_8)); + } + + private String readToString(InputStreamReader i) { + try { + return CharStreams.toString(i); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/unirest-modules-mocks/src/test/java/kong/tests/RegistrationTest.java b/unirest-modules-mocks/src/test/java/kong/tests/RegistrationTest.java new file mode 100644 index 000000000..ae686d815 --- /dev/null +++ b/unirest-modules-mocks/src/test/java/kong/tests/RegistrationTest.java @@ -0,0 +1,62 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.tests; + +import kong.unirest.core.MockClient; +import kong.unirest.core.Unirest; +import kong.unirest.core.UnirestInstance; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; + +class RegistrationTest { + + @Test + void canRegisterThePrimaryInstance() { + MockClient client = MockClient.register(); + assertSame(client, Unirest.primaryInstance().config().getClient()); + assertSame(client, Unirest.primaryInstance().config().getClient()); + + MockClient.clear(); + + assertFalse(Unirest.primaryInstance().config().getClient() instanceof MockClient); + assertFalse(Unirest.primaryInstance().config().getClient() instanceof MockClient); + } + + @Test + void canRegisterInstances() { + UnirestInstance i = Unirest.spawnInstance(); + MockClient client = MockClient.register(i); + assertSame(client, i.config().getClient()); + assertSame(client, i.config().getClient()); + + MockClient.clear(i); + + assertFalse(i.config().getClient() instanceof MockClient); + assertFalse(i.config().getClient() instanceof MockClient); + } +} diff --git a/unirest-modules-mocks/src/test/java/kong/tests/ResponseHandlerTest.java b/unirest-modules-mocks/src/test/java/kong/tests/ResponseHandlerTest.java new file mode 100644 index 000000000..7782edbfd --- /dev/null +++ b/unirest-modules-mocks/src/test/java/kong/tests/ResponseHandlerTest.java @@ -0,0 +1,50 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.tests; + +import kong.unirest.core.ExpectedResponse; +import kong.unirest.core.HttpMethod; +import kong.unirest.core.HttpResponse; +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ResponseHandlerTest extends Base { + + @Test + void canSetAFullRequestHandler() { + client.expect(HttpMethod.GET, "/fruit") + .thenReturn(r -> ExpectedResponse.of(200) + .thenReturn("Hello Jane") + .withHeader("foo", "bar")); + + HttpResponse string = Unirest.get("/fruit").asString(); + assertEquals("Hello Jane", string.getBody()); + assertEquals("bar", string.getHeaders().getFirst("foo")); + assertEquals(200, string.getStatus()); + } +} diff --git a/unirest-modules-mocks/src/test/java/kong/tests/VerifyTimesTest.java b/unirest-modules-mocks/src/test/java/kong/tests/VerifyTimesTest.java new file mode 100644 index 000000000..038bf8798 --- /dev/null +++ b/unirest-modules-mocks/src/test/java/kong/tests/VerifyTimesTest.java @@ -0,0 +1,151 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.tests; + +import kong.unirest.core.*; +import org.junit.jupiter.api.Test; + +import static kong.unirest.core.HttpMethod.GET; +import static kong.unirest.core.HttpMethod.POST; +import static org.junit.jupiter.api.Assertions.*; + +public class VerifyTimesTest extends Base { + @Test + void assertJustOne() { + var response = client.expect(HttpMethod.POST, path) + .thenReturn(MockResponse.of(500, null)); + + assertThrows(UnirestAssertion.class, response::verify); + + Unirest.post(path).asEmpty(); + + assertDoesNotThrow(() -> response.verify()); + } + + @Test + void assertFromTheExpect() { + var expect = client.expect(POST, path); + expect.thenReturn(MockResponse.of(500, null)); + + assertThrows(UnirestAssertion.class, expect::verify); + + Unirest.post(path).asEmpty(); + + assertDoesNotThrow(() -> expect.verify()); + } + + @Test + void assertJustOneWithFunctionalInterface() { + var response = ExpectedResponse.of(200) + .thenReturn("Hello Jane") + .withHeader("foo", "bar"); + + client.expect(HttpMethod.POST, path) + .thenReturn(r -> response); + + var ex = assertThrows(UnirestAssertion.class, response::verify); + assertEquals("An expectation was never invoked!", ex.getMessage()); + + Unirest.post(path).asEmpty(); + + assertDoesNotThrow(() -> response.verify()); + } + + @Test + void verifyExactlyOnce() { + var expect = client.expect(GET, path).thenReturn("hi"); + + Unirest.get(path).asEmpty(); + + assertDoesNotThrow(() -> expect.verify(Times.exactlyOnce())); + + Unirest.get(path).asEmpty(); + + var ex = assertThrows(UnirestAssertion.class, () -> expect.verify(Times.exactly(1))); + assertEquals("Expected exactly 1 invocations but got 2\n" + + "GET http://basic\n", ex.getMessage()); + } + + @Test + void verifyAtLeast() { + var expect = client.expect(GET, path).thenReturn("hi"); + Unirest.get(path).asEmpty(); + + var ex = assertThrows(UnirestAssertion.class, () -> expect.verify(Times.atLeast(2))); + assertEquals("Expected at least 2 invocations but got 1\n" + + "GET http://basic\n", ex.getMessage()); + + Unirest.get(path).asEmpty(); + + assertDoesNotThrow(() -> expect.verify(Times.atLeast(2))); + } + + @Test + void verifyNever() { + var expect = client.expect(GET, path).thenReturn("hi"); + + assertDoesNotThrow(() -> expect.verify(Times.never())); + + Unirest.get(path).asEmpty(); + + var ex = assertThrows(UnirestAssertion.class, () -> expect.verify(Times.never())); + assertEquals("Expected exactly 0 invocations but got 1\n" + + "GET http://basic\n", ex.getMessage()); + + Unirest.get(path).asEmpty(); + } + + @Test + void verifyAtMost() { + var expect = client.expect(GET, path).thenReturn("hi"); + + assertDoesNotThrow(() -> expect.verify(Times.atMost(1))); + + Unirest.get(path).asEmpty(); + assertDoesNotThrow(() -> expect.verify(Times.atMost(1))); + + Unirest.get(path).asEmpty(); + + var ex = assertThrows(UnirestAssertion.class, () -> expect.verify(Times.atMost(1))); + assertEquals("Expected no more than 1 invocations but got 2\n" + + "GET http://basic\n", ex.getMessage()); + + Unirest.get(path).asEmpty(); + } + + @Test + void buildExpectationTimesIntoChain() { + var expect = client.expect(GET, path).times(Times.never()).thenReturn("hi"); + + assertDoesNotThrow(() -> expect.verify()); + + Unirest.get(path).asEmpty(); + + var ex = assertThrows(UnirestAssertion.class, () -> expect.verify()); + assertEquals("Expected exactly 0 invocations but got 1\n" + + "GET http://basic\n", ex.getMessage()); + } +} diff --git a/unirest-modules-mocks/src/test/java/kong/unirest/core/MockListenerTest.java b/unirest-modules-mocks/src/test/java/kong/unirest/core/MockListenerTest.java new file mode 100644 index 000000000..e95bcd39b --- /dev/null +++ b/unirest-modules-mocks/src/test/java/kong/unirest/core/MockListenerTest.java @@ -0,0 +1,132 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; + +import java.net.http.WebSocket; +import java.nio.ByteBuffer; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +class MockListenerTest { + WebSocket socket; + MockListener listener; + + @BeforeEach + void setUp() { + socket = mock(WebSocket.class); + listener = new MockListener(); + } + + @Test + void assertPing() { + var wrap = ByteBuffer.wrap("hi".getBytes()); + + assertAssertionThrown(() -> listener.assertPing(wrap), + "Expected Ping Call with buffer java.nio.HeapByteBuffer[pos=0 lim=2 cap=2] but got null"); + + listener.onPing(socket, wrap); + + listener.assertPing(wrap); + + verify(socket).sendPong(wrap); + } + + @Test + void assertPong() { + var wrap = ByteBuffer.wrap("hi".getBytes()); + + assertAssertionThrown(() -> listener.assertPong(wrap), + "Expected Pong Message java.nio.HeapByteBuffer[pos=0 lim=2 cap=2] but got null"); + + listener.onPong(socket, wrap); + + listener.assertPong(wrap); + } + + @Test + void assertClosed() { + listener.onOpen(socket); + + assertAssertionThrown(() -> listener.assertIsClosed(418, "I am a teapot"), + "Expected to be closed but was not"); + + listener.onClose(socket, 418, "I am a teapot"); + + assertAssertionThrown(() -> listener.assertIsClosed(400, "Sad"), + "Incorrect Closed Status/Message. Expected [400 : Sad] but got [418 : I am a teapot]"); + + listener.assertIsClosed(418, "I am a teapot"); + } + + @Test + void assertTextMessage() { + assertAssertionThrown(() -> listener.assertReceivedMessage("Hail Sithis!", false), + "Did not receive any message: [Hail Sithis! : false] "); + + listener.onText(socket, "Hail Sithis!", true); + + assertAssertionThrown(() -> listener.assertReceivedMessage("Hail Sithis!", false), + "Did not receive any message: [Hail Sithis! : false] "); + + listener.assertReceivedMessage("Hail Sithis!", true); + } + + @Test + void assertBinaryMessage() { + var buf = ByteBuffer.wrap("Hail Sithis!".getBytes()); + + assertAssertionThrown(() -> listener.assertReceivedMessage(buf, false), + "Did not receive any message: [java.nio.HeapByteBuffer[pos=0 lim=12 cap=12] : false] "); + + listener.onBinary(socket, buf, true); + + assertAssertionThrown(() -> listener.assertReceivedMessage(buf, false), + "Did not receive any message: [java.nio.HeapByteBuffer[pos=0 lim=12 cap=12] : false] "); + + listener.assertReceivedMessage(buf, true); + } + + @Test + void assertOpen() { + assertAssertionThrown(() -> listener.assertIsOpen(), + "Expected socket to be open but was closed."); + listener.onOpen(socket); + + listener.assertIsOpen(); + } + + private void assertAssertionThrown(Executable ex, String errorMessage) { + var a = assertThrows(UnirestAssertion.class, ex); + assertEquals(errorMessage, a.getMessage()); + } +} \ No newline at end of file diff --git a/unirest-modules-mocks/src/test/java/kong/unirest/core/MockResponseTest.java b/unirest-modules-mocks/src/test/java/kong/unirest/core/MockResponseTest.java new file mode 100644 index 000000000..4f3b5f966 --- /dev/null +++ b/unirest-modules-mocks/src/test/java/kong/unirest/core/MockResponseTest.java @@ -0,0 +1,79 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class MockResponseTest { + + @Test + void canCreateASuccessfulMockResponse() { + var response = MockResponse.ok("Hi Mom"); + assertEquals(200, response.getStatus()); + assertEquals("Hi Mom", response.getBody()); + } + + @Test + void canAddHeaders() { + var response = MockResponse.ok("Hi Mom") + .withHeader("Accept", "application/xml"); + + assertEquals("application/xml", response.getHeaders().getFirst("Accept")); + } + + @Test + void badRequestWithParsingException() { + var response = MockResponse.bad("Hi Mom") + .failedToParse(); + + assertEquals(400, response.getStatus()); + assertEquals("Hi Mom", response.getParsingError().get().getOriginalBody()); + } + + @Test + void canSetConfigOptions() { + var response = MockResponse.bad("Hi Mom") + .withConfigOptions(c -> c.setObjectMapper(new ObjectMapper() { + @Override + public T readValue(String value, Class valueType) { + return (T)new TransformedError(); + } + + @Override + public String writeValue(Object value) { + return null; + } + })); + + assertEquals("Transformed!", response.mapError(TransformedError.class).message); + } + + class TransformedError { + String message = "Transformed!"; + } +} \ No newline at end of file diff --git a/unirest-modules-mocks/src/test/java/kong/unirest/core/MockWebSocketResponseTest.java b/unirest-modules-mocks/src/test/java/kong/unirest/core/MockWebSocketResponseTest.java new file mode 100644 index 000000000..90908c771 --- /dev/null +++ b/unirest-modules-mocks/src/test/java/kong/unirest/core/MockWebSocketResponseTest.java @@ -0,0 +1,139 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import kong.tests.Base; +import kong.unirest.core.MockListener; +import kong.unirest.core.Unirest; +import kong.unirest.core.WebSocketResponse; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.net.http.WebSocket; +import java.nio.ByteBuffer; + +class MockWebSocketResponseTest extends Base { + + private MockListener listener; + + @BeforeEach + public void setUp() { + super.setUp(); + listener = new MockListener(); + } + + @Test + void sendTextFromServer() { + createConnection(); + + client.serversSocket().getSocket().sendText("All hail the listener", false); + + listener.assertReceivedMessage("All hail the listener", false); + } + + @Test + void sendPingFromServer() { + createConnection(); + + var buffer = ByteBuffer.wrap("ping".getBytes()); + client.serversSocket().getSocket().sendPing(buffer); + + client.serversSocket().getListener().assertPong(buffer); + } + + @Test + void serverSendsBinary(){ + createConnection(); + var bytes = ByteBuffer.wrap("hi".getBytes()); + client.serversSocket().getSocket().sendBinary(bytes, false); + listener.assertReceivedMessage(bytes, false); + } + + @Test + void serverSendsClose() { + createConnection(); + + client.serversSocket().getSocket().sendClose(418, "I am a teapot"); + + listener.assertIsClosed(418, "I am a teapot"); + } + + @Test + void localSendsText() throws Exception { + createConnection().socket().get().sendText("Hail Sithis!", false); + + client.serversSocket() + .getListener() + .assertReceivedMessage("Hail Sithis!", false); + } + + @Test + void localSendsBinary() throws Exception { + var sithis = ByteBuffer.wrap("Hail Sithis!".getBytes()); + createConnection().socket().get().sendBinary(sithis, false); + + client.serversSocket() + .getListener() + .assertReceivedMessage(sithis, false); + } + + @Test + void localSendsClose() throws Exception { + createConnection().socket().get().sendClose(418, "I am a teapot"); + + client.serversSocket() + .getListener() + .assertIsClosed(418, "I am a teapot"); + } + + @Test + void localSendsPingPong() throws Exception { + var buffer = ByteBuffer.wrap("ping".getBytes()); + createConnection().socket().get().sendPing(buffer); + + listener.assertPong(buffer); + } + + @Test + void onConnectFiresFromLocal() throws Exception { + var listener = new MockListener(){ + @Override + public void onOpen(WebSocket webSocket) { + webSocket.sendText("Hello Mother!", false); + } + }; + Unirest.webSocket("ws://localhost") + .connect(listener) + .socket() + .get(); + + client.serversSocket().getListener().assertReceivedMessage("Hello Mother!", false); + } + + private WebSocketResponse createConnection() { + return Unirest.webSocket("ws://localhost").connect(listener); + } +} \ No newline at end of file diff --git a/unirest-modules-mocks/src/test/java/kong/unirest/core/TimesTest.java b/unirest-modules-mocks/src/test/java/kong/unirest/core/TimesTest.java new file mode 100644 index 000000000..fa7d6c72b --- /dev/null +++ b/unirest-modules-mocks/src/test/java/kong/unirest/core/TimesTest.java @@ -0,0 +1,85 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class TimesTest { + + @Test + void exactly() { + assertPass(Times.exactlyOnce().matches(1)); + assertFail(Times.exactlyOnce().matches(2), "Expected exactly 1 invocations but got 2"); + assertFail(Times.exactlyOnce().matches(0), "Expected exactly 1 invocations but got 0"); + } + + @Test + void exactlySpecified() { + assertPass(Times.exactly(42).matches(42)); + assertFail(Times.exactly(1).matches(2), "Expected exactly 1 invocations but got 2"); + assertFail(Times.exactly(42).matches(2), "Expected exactly 42 invocations but got 2"); + assertFail(Times.exactly(1).matches(0), "Expected exactly 1 invocations but got 0"); + } + + @Test + void atLeast() { + assertPass(Times.atLeastOnce().matches(1)); + assertPass(Times.atLeastOnce().matches(2)); + assertFail(Times.atLeastOnce().matches(0), "Expected at least 1 invocations but got 0"); + } + + @Test + void atLeastSpecific() { + assertPass(Times.atLeast(1).matches(1)); + assertPass(Times.atLeast(1).matches(2)); + assertFail(Times.atLeast(1).matches(0), "Expected at least 1 invocations but got 0"); + } + + @Test + void never() { + assertPass(Times.never().matches(0)); + assertFail(Times.never().matches(2), "Expected exactly 0 invocations but got 2"); + } + + @Test + void atMost(){ + assertPass(Times.atMost(2).matches(1)); + assertPass(Times.atMost(2).matches(2)); + assertFail(Times.atMost(2).matches(3), "Expected no more than 2 invocations but got 3"); + } + + void assertPass(Times.EvaluationResult matches){ + assertTrue(matches.isSuccess()); + assertNull(matches.getMessage()); + } + + void assertFail(Times.EvaluationResult matches, String message) { + assertFalse(matches.isSuccess()); + assertEquals(message, matches.getMessage()); + } +} \ No newline at end of file diff --git a/unirest/pom.xml b/unirest/pom.xml new file mode 100644 index 000000000..e01fee924 --- /dev/null +++ b/unirest/pom.xml @@ -0,0 +1,18 @@ + + + 4.0.0 + + com.konghq + unirest-java-parent + 4.10.3-SNAPSHOT + + + Simplified, lightweight HTTP client library. + unirest-java-core + unirest-java-core + jar + + + ${project.parent.basedir} + + diff --git a/unirest/src/main/java/kong/unirest/core/BaseRequest.java b/unirest/src/main/java/kong/unirest/core/BaseRequest.java new file mode 100644 index 000000000..c0356a8ab --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/BaseRequest.java @@ -0,0 +1,478 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.io.File; +import java.net.http.HttpClient; +import java.nio.file.CopyOption; +import java.time.Instant; +import java.util.Collection; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +import static kong.unirest.core.CallbackFuture.wrap; + +abstract class BaseRequest implements HttpRequest { + + private Instant creation = Util.now(); + private int callCount = 0; + private Optional objectMapper = Optional.empty(); + private String responseEncoding; + protected Headers headers = new Headers(); + protected final Config config; + protected HttpMethod method; + protected Path url; + private Integer connectTimeout; + private ProgressMonitor downloadMonitor; + private HttpClient.Version version; + + BaseRequest(BaseRequest httpRequest) { + this.config = httpRequest.config; + this.method = httpRequest.method; + this.url = httpRequest.url; + this.headers.putAll(httpRequest.headers); + this.connectTimeout = httpRequest.connectTimeout; + this.objectMapper = httpRequest.objectMapper; + this.version = httpRequest.version; + this.downloadMonitor = httpRequest.downloadMonitor; + } + + BaseRequest(Config config, HttpMethod method, String url) { + this.config = config; + this.method = method; + this.url = new Path(url, config.getDefaultBaseUrl()); + headers.putAll(config.getDefaultHeaders()); + } + + @Override + public R routeParam(String name, String value) { + url.param(name, value); + return (R) this; + } + + @Override + public R routeParam(Map params) { + url.param(params); + return (R) this; + } + + @Override + public R basicAuth(String username, String password) { + this.headers.setBasicAuth(username, password); + return (R) this; + } + + @Override + public R accept(String value) { + this.headers.accepts(value); + return (R) this; + } + + @Override + public R header(String name, String value) { + this.headers.add(name.trim(), value); + return (R) this; + } + + @Override + public R headerReplace(String name, String value) { + this.headers.replace(name, value); + return (R) this; + } + + @Override + public R headers(Map headerMap) { + this.headers.add(headerMap); + return (R) this; + } + + @Override + public R headersReplace(Map headerMap) { + this.headers.replace(headerMap); + return (R) this; + } + + @Override + public R responseEncoding(String encoding) { + this.responseEncoding = encoding; + return (R) this; + } + + @Override + public R cookie(String name, String value) { + this.headers.cookie(new Cookie(name, value)); + return (R) this; + } + + @Override + public R cookie(Cookie cookie) { + this.headers.cookie(cookie); + return (R) this; + } + + @Override + public R cookie(Collection cookies) { + this.headers.cookie(cookies); + return (R)this; + } + + @Override + public R queryString(String name, Collection value) { + url.queryString(name, value); + return (R) this; + } + + @Override + public R queryString(String name, Object value) { + url.queryString(name, value); + return (R) this; + } + + @Override + public R queryString(Map parameters) { + url.queryString(parameters); + return (R) this; + } + + @Override + public R requestTimeout(int millies) { + this.connectTimeout = millies; + return (R) this; + } + + @Override + public R withObjectMapper(ObjectMapper mapper) { + Objects.requireNonNull(mapper, "ObjectMapper may not be null"); + this.objectMapper = Optional.of(mapper); + return (R) this; + } + + @Override + public R downloadMonitor(ProgressMonitor monitor) { + this.downloadMonitor = monitor; + return (R) this; + } + + public ProgressMonitor getDownloadMonitor(){ + return this.downloadMonitor; + } + + @Override + public R version(HttpClient.Version value) { + this.version = value; + return (R) this; + } + + @Override + public HttpResponse asEmpty() { + return request(BasicResponse::new, Empty.class); + } + + @Override + public CompletableFuture> asEmptyAsync() { + return requestAsync(this, BasicResponse::new, new CompletableFuture<>(), Empty.class); + } + + @Override + public CompletableFuture> asEmptyAsync(Callback callback) { + return requestAsync(this, BasicResponse::new, wrap(callback), Empty.class); + } + + @Override + public HttpResponse asString() throws UnirestException { + return request(r -> new StringResponse(r, responseEncoding), String.class); + } + + @Override + public CompletableFuture> asStringAsync() { + return requestAsync(this, r -> new StringResponse(r, responseEncoding), new CompletableFuture<>(), String.class); + } + + @Override + public CompletableFuture> asStringAsync(Callback callback) { + return requestAsync(this, r -> new StringResponse(r, responseEncoding), wrap(callback), String.class); + } + + @Override + public HttpResponse asBytes() { + return request(r -> new ByteResponse(r, downloadMonitor), byte[].class); + } + + @Override + public CompletableFuture> asBytesAsync() { + return requestAsync(this, (RawResponse r) -> new ByteResponse(r, downloadMonitor), new CompletableFuture<>(), byte[].class); + } + + @Override + public CompletableFuture> asBytesAsync(Callback callback) { + return requestAsync(this, (RawResponse r) -> new ByteResponse(r, downloadMonitor), wrap(callback), byte[].class); + } + + @Override + public HttpResponse asJson() throws UnirestException { + return request(JsonResponse::new, JsonNode.class); + } + + @Override + public CompletableFuture> asJsonAsync() { + return requestAsync(this, JsonResponse::new, new CompletableFuture<>(), JsonNode.class); + } + + @Override + public CompletableFuture> asJsonAsync(Callback callback) { + return requestAsync(this, JsonResponse::new, wrap(callback), JsonNode.class); + } + + @Override + public HttpResponse asObject(Class responseClass) throws UnirestException { + return request(r -> new ObjectResponse(getObjectMapper(), r, responseClass), responseClass); + } + + @Override + public HttpResponse asObject(GenericType genericType) throws UnirestException { + return request(r -> new ObjectResponse(getObjectMapper(), r, genericType), genericType.getTypeClass()); + } + + @Override + public HttpResponse asObject(Function function) { + return request(funcResponse(function), Object.class); + } + + @Override + public CompletableFuture> asObjectAsync(Function function) { + return requestAsync(this, funcResponse(function), new CompletableFuture<>(), JsonNode.class); + } + + @Override + public CompletableFuture> asObjectAsync(Class responseClass) { + return requestAsync(this, + r -> new ObjectResponse(getObjectMapper(), r, responseClass), + new CompletableFuture<>(), + responseClass); + } + + @Override + public CompletableFuture> asObjectAsync(Class responseClass, Callback callback) { + return requestAsync(this, + r -> new ObjectResponse<>(getObjectMapper(), r, responseClass), + wrap(callback), + responseClass); + } + + @Override + public CompletableFuture> asObjectAsync(GenericType genericType) { + return requestAsync(this, + r -> new ObjectResponse<>(getObjectMapper(), r, genericType), + new CompletableFuture<>(), + genericType.getTypeClass()); + } + + @Override + public CompletableFuture> asObjectAsync(GenericType genericType, Callback callback) { + return requestAsync(this, + r -> new ObjectResponse<>(getObjectMapper(), r, genericType), + wrap(callback), + genericType.getTypeClass()); + } + + private Function> funcResponse(Function function) { + return r -> new BasicResponse<>(r, function.apply(r)); + } + + @Override + public void thenConsume(Consumer consumer) { + request(getConsumer(consumer), Object.class); + } + + @Override + public void thenConsumeAsync(Consumer consumer) { + requestAsync(this, getConsumer(consumer), new CompletableFuture<>(), Object.class); + } + + @Override + public HttpResponse asFile(String path, CopyOption... copyOptions) { + return request(r -> new FileResponse(r, path, downloadMonitor, copyOptions), File.class); + } + + @Override + public CompletableFuture> asFileAsync(String path, CopyOption... copyOptions) { + return requestAsync(this, + r -> new FileResponse(r, path, downloadMonitor, copyOptions), + new CompletableFuture<>(), + File.class); + } + + @Override + public CompletableFuture> asFileAsync(String path, Callback callback, CopyOption... copyOptions) { + return requestAsync(this, + r -> new FileResponse(r, path, downloadMonitor, copyOptions), + wrap(callback), + File.class); + } + + @Override + public PagedList asPaged(Function mappingFunction, Function, String> linkExtractor) { + PagedList all = new PagedList<>(); + String nextLink = this.getUrl(); + do { + this.url = new Path(nextLink, config.getDefaultBaseUrl()); + BaseRequest t = RequestFactory.copy(this); + HttpResponse next = mappingFunction.apply(t); + all.add(next); + nextLink = linkExtractor.apply(next); + } while (!Util.isNullOrEmpty(nextLink)); + return all; + } + + + private HttpResponse request(Function> transformer, Class resultType){ + HttpResponse response = config.getClient().request(this, transformer, resultType); + + if(config.isAutomaticRetryAfter()) { + callCount++; + var retryAfter = config.getRetryStrategy(); + if(retryAfter.isRetryable(response) && callCount < config.maxRetries()) { + long waitTime = retryAfter.getWaitTime(response); + if (waitTime > 0) { + retryAfter.waitFor(waitTime); + return request(transformer, resultType); + } + } + } + return response; + } + + private CompletableFuture> requestAsync(HttpRequest request, + Function> transformer, + CompletableFuture> callback, + Class resultType){ + var asyncR = config.getClient().request(request, transformer, callback, resultType); + if(config.isAutomaticRetryAfter()){ + return asyncR.thenApplyAsync(response -> { + callCount++; + var retryAfter = config.getRetryStrategy(); + if(retryAfter.isRetryable(response) && callCount < config.maxRetries()) { + long waitTime = retryAfter.getWaitTime(response); + if (waitTime > 0) { + retryAfter.waitFor(waitTime); + try { + return requestAsync(this, transformer, callback, resultType).get(); + } catch (Exception e) { + throw new UnirestException(e); + } + } + } + return response; + }); + } + return asyncR; + } + + + private Function> getConsumer(Consumer consumer) { + return r -> { + consumer.accept(r); + return new BasicResponse<>(r); + }; + } + + @Override + public HttpMethod getHttpMethod() { + return method; + } + + @Override + public String getUrl() { + return url.toString(); + } + + + + @Override + public Headers getHeaders() { + return headers; + } + + protected ObjectMapper getObjectMapper() { + return objectMapper.orElseGet(config::getObjectMapper); + } + + @Override + public Integer getRequestTimeout() { + return valueOr(connectTimeout, config::getRequestTimeout); + } + + @Override + public HttpRequestSummary toSummary() { + return new RequestSummary(this); + } + + @Override + public Instant getCreationTime() { + return creation; + } + + @Override + public HttpClient.Version getVersion() { + return version; + } + + private T valueOr(T x, Supplier o) { + if (x != null) { + return x; + } + return o.get(); + } + + Path getPath() { + return url; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BaseRequest that = (BaseRequest) o; + return Objects.equals(headers, that.headers) && + Objects.equals(method, that.method) && + Objects.equals(url, that.url); + } + + @Override + public int hashCode() { + return Objects.hash(headers, method, url); + } + +} diff --git a/unirest/src/main/java/kong/unirest/core/BaseResponse.java b/unirest/src/main/java/kong/unirest/core/BaseResponse.java new file mode 100644 index 000000000..10f41a1a7 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/BaseResponse.java @@ -0,0 +1,182 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.nio.charset.StandardCharsets; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; + +abstract class BaseResponse implements HttpResponse { + + private final Headers headers; + private final String statusText; + private final int statusCode; + private final HttpRequestSummary reqSummary; + private Optional parsingerror = Optional.empty(); + private final Config config; + private Cookies cookies; + + + protected BaseResponse(RawResponse response) { + this.headers = response.getHeaders(); + // Unirest decompresses the content, so this should be removed as it is + // no longer encoded + this.headers.remove("Content-Encoding", "gzip"); + this.statusCode = response.getStatus(); + this.statusText = response.getStatusText(); + this.config = response.getConfig(); + this.reqSummary = response.getRequestSummary(); + } + + protected BaseResponse(BaseResponse other) { + this.headers = other.headers; + this.statusCode = other.statusCode; + this.statusText = other.statusText; + this.config = other.config; + this.reqSummary = other.reqSummary; + } + + @Override + public int getStatus() { + return statusCode; + } + + @Override + public String getStatusText() { + return statusText; + } + + @Override + public Headers getHeaders() { + return headers; + } + + @Override + public abstract T getBody(); + + @Override + public Optional getParsingError() { + return parsingerror; + } + + @Override + public V mapBody(Function func) { + return func.apply(getBody()); + } + + @Override + public HttpResponse map(Function func) { + return new BasicResponse(this, mapBody(func)); + } + + protected void setParsingException(String originalBody, RuntimeException e) { + parsingerror = Optional.of(new UnirestParsingException(originalBody, e)); + } + + @Override + public boolean isSuccess() { + return getStatus() >= 200 && getStatus() < 300 && !getParsingError().isPresent(); + } + + @Override + public HttpResponse ifSuccess(Consumer> consumer) { + if (isSuccess()) { + consumer.accept(this); + } + return this; + } + + @Override + public HttpResponse ifFailure(Consumer> consumer) { + if (!isSuccess()) { + consumer.accept(this); + } + return this; + } + + @Override + public E mapError(Class errorClass) { + if (!isSuccess()) { + String errorBody = getErrorBody(); + if(String.class.equals(errorClass)){ + return (E) errorBody; + } + try { + return config.getObjectMapper().readValue(errorBody, errorClass); + } catch (RuntimeException e) { + setParsingException(errorBody, e); + } + } + return null; + } + + private String getErrorBody() { + if (getParsingError().isPresent()) { + return getParsingError().get().getOriginalBody(); + } else if (getRawBody() != null) { + return getRawBody(); + } + T body = getBody(); + if (body == null) { + return null; + } + try { + if(body instanceof byte[]){ + return new String((byte[])body, StandardCharsets.UTF_8); + } + return config.getObjectMapper().writeValue(body); + } catch (Exception e) { + return String.valueOf(body); + } + } + + @Override + public HttpResponse ifFailure(Class errorClass, Consumer> consumer) { + if (!isSuccess()) { + E error = mapError(errorClass); + BasicResponse br = new BasicResponse(this, error); + getParsingError().ifPresent(p -> br.setParsingException(p.getOriginalBody(), p)); + consumer.accept(br); + } + return this; + } + + @Override + public Cookies getCookies() { + if (cookies == null) { + cookies = new Cookies(headers.get("set-cookie")); + } + return cookies; + } + + @Override + public HttpRequestSummary getRequestSummary() { + return reqSummary; + } + + protected abstract String getRawBody(); +} diff --git a/unirest/src/main/java/kong/unirest/core/BasicResponse.java b/unirest/src/main/java/kong/unirest/core/BasicResponse.java new file mode 100644 index 000000000..2ad4f6a07 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/BasicResponse.java @@ -0,0 +1,60 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +public class BasicResponse extends BaseResponse { + private final T body; + + BasicResponse(BaseResponse response, T body) { + super(response); + this.body = body; + } + + public BasicResponse(RawResponse httpResponse, T body) { + super(httpResponse); + this.body = body; + } + + public BasicResponse(RawResponse httpResponse) { + super(httpResponse); + this.body = null; + } + + public BasicResponse(RawResponse httpResponse, String ogBody, RuntimeException ex) { + this(httpResponse, null); + setParsingException(ogBody, ex); + } + + @Override + public T getBody() { + return body; + } + + @Override + protected String getRawBody() { + return null; + } +} diff --git a/unirest/src/main/java/kong/unirest/core/Body.java b/unirest/src/main/java/kong/unirest/core/Body.java new file mode 100644 index 000000000..f13239104 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/Body.java @@ -0,0 +1,145 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.Collections; + +/** + * Represents the body of an HTTP request. + *

+ * A body can be either a multipart body (containing multiple parts such as form fields + * and file uploads) or an entity body (containing a single piece of content like JSON or XML). + *

+ *

+ * Implementations of this interface are used internally by Unirest to serialize + * request content for transmission. + *

+ * + * @see BodyPart + * @see MultipartBody + * @see MultipartMode + */ +public interface Body { + + /** + * Indicates whether this body is a multipart body. + *

+ * Multipart bodies contain multiple parts separated by boundaries and are typically + * used for form submissions that include file uploads. + * + * @return {@code true} if this is a multipart body, {@code false} otherwise + */ + boolean isMultiPart(); + + /** + * Indicates whether this body is an entity body. + *

+ * Entity bodies contain a single piece of content such as JSON, XML, or plain text. + * + * @return {@code true} if this is an entity body, {@code false} otherwise + */ + boolean isEntityBody(); + + /** + * Returns the character encoding used for this body. + * + * @return the {@link Charset} for encoding the body content; defaults to UTF-8 + */ + default Charset getCharset(){ + return StandardCharsets.UTF_8; + } + + /** + * Returns all parts of a multipart body. + * + * @return a collection of {@link BodyPart} objects representing each part; + * returns an empty collection if this is not a multipart body + */ + default Collection multiParts(){ + return Collections.emptyList(); + } + + /** + * Returns the single body part for an entity body. + * + * @return the {@link BodyPart} containing the entity content, + * or {@code null} if this is not an entity body + */ + default BodyPart uniPart(){ + return null; + } + + /** + * Returns the multipart mode used for encoding this body. + * + * @return the {@link MultipartMode} determining how parts are encoded; + * defaults to {@link MultipartMode#BROWSER_COMPATIBLE} + * @see MultipartMode + */ + default MultipartMode getMode(){ + return MultipartMode.BROWSER_COMPATIBLE; + } + + /** + * Returns the progress monitor for tracking upload progress. + * + * @return the {@link ProgressMonitor} for this body, or {@code null} if none is set + * @see ProgressMonitor + */ + default ProgressMonitor getMonitor(){ + return null; + } + + /** + * Returns the boundary string used to separate parts in a multipart body. + *

+ * The boundary is a unique string that delimits each part in the multipart content. + * + * @return the boundary string, or {@code null} if not applicable or auto-generated + */ + default String getBoundary() { + return null; + } + + /** + * Retrieves a specific body part by its field name. + * + * @param name the name of the field to retrieve + * @return the {@link BodyPart} with the specified name, + * or {@code null} if no part with that name exists + */ + default BodyPart getField(String name){ + return multiParts() + .stream() + .filter(part -> part.getName().equals(name)) + .findFirst() + .orElse(null); + } +} diff --git a/unirest/src/main/java/kong/unirest/core/BodyPart.java b/unirest/src/main/java/kong/unirest/core/BodyPart.java new file mode 100644 index 000000000..1248f260e --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/BodyPart.java @@ -0,0 +1,173 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.nio.charset.StandardCharsets; + +/** + * Represents a single part in a multipart HTTP request body. + *

+ * This abstract class encapsulates the data and metadata for one part of a multipart + * request, including the part's name, value, content type, and optional headers. + * Body parts can represent either form fields or file uploads. + *

+ *

+ * Subclasses must implement the {@link #isFile()} method to indicate whether + * the part represents a file upload or a regular form field. + *

+ * + * @param the type of the value contained in this body part + * @see MultipartBody + * @see ContentType + */ +public abstract class BodyPart implements Comparable { ; + private final String name; + private final T value; + private final String contentType; + private final Class partType; + private final Headers headers; + + /** + * Constructs a new BodyPart with the specified value, name, and content type. + * + * @param value the value of this body part + * @param name the name (field name) of this body part + * @param contentType the MIME content type of this body part, or {@code null} to use defaults + */ + protected BodyPart(T value, String name, String contentType) { + this(value, name, contentType, null); + } + + /** + * Constructs a new BodyPart with the specified value, name, content type, and headers. + * + * @param value the value of this body part + * @param name the name (field name) of this body part + * @param contentType the MIME content type of this body part, or {@code null} to use defaults + * @param headers additional headers for this body part, or {@code null} if none + */ + protected BodyPart(T value, String name, String contentType, Headers headers) { + this.name = name; + this.value = value; + this.contentType = contentType; + this.partType = value.getClass(); + this.headers = headers; + } + + /** + * Returns the value of this body part. + * + * @return the value contained in this body part + */ + public T getValue() { + return value; + } + + /** + * Returns the runtime class of the value. + * + * @return the {@link Class} object representing the type of the value + */ + public Class getPartType(){ + return partType; + } + + /** + * Returns the content type of this body part. + *

+ * If no content type was explicitly set: + *

    + *
  • For file parts: returns {@code application/octet-stream}
  • + *
  • For non-file parts: returns {@code application/x-www-form-urlencoded; charset=UTF-8}
  • + *
+ * + * @return the MIME content type string for this body part + */ + public String getContentType() { + if(contentType == null){ + if(isFile()){ + return ContentType.APPLICATION_OCTET_STREAM.toString(); + } + return ContentType.APPLICATION_FORM_URLENCODED.withCharset(StandardCharsets.UTF_8).toString(); + } + return contentType; + } + + /** + * Returns the name (field name) of this body part. + * + * @return the name of this body part, or an empty string if no name was set + */ + public String getName() { + return name == null ? "" : name; + } + + /** + * Returns the file name for this body part. + *

+ * For file uploads, this typically represents the original file name. + * For regular form fields, this returns the same value as {@link #getName()}. + * + * @return the file name of this body part + */ + public String getFileName(){ + return name; + } + + /** + * Compares this body part with another for ordering purposes. + *

+ * Body parts are compared by their names in natural string order. + * If the other object is not a {@link BodyPart}, returns 0. + * + * @param o the object to compare with + * @return a negative integer, zero, or a positive integer as this body part's + * name is less than, equal to, or greater than the other's name + */ + @Override + public int compareTo(Object o) { + if(o instanceof BodyPart){ + return getName().compareTo(((BodyPart)o).getName()); + } + return 0; + } + + /** + * Indicates whether this body part represents a file upload. + * + * @return {@code true} if this part represents a file, {@code false} for regular form fields + */ + abstract public boolean isFile(); + + /** + * Returns any additional headers associated with this body part. + * + * @return the headers for this body part, or {@code null} if none were set + */ + public Headers getHeaders() { + return headers; + } +} diff --git a/unirest/src/main/java/kong/unirest/core/ByteArrayPart.java b/unirest/src/main/java/kong/unirest/core/ByteArrayPart.java new file mode 100644 index 000000000..f02cb2620 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/ByteArrayPart.java @@ -0,0 +1,55 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + + +public class ByteArrayPart extends BodyPart { + private final String fileName; + + ByteArrayPart(String name, byte[] bytes, ContentType contentType, String fileName) { + this(name, bytes, contentType, fileName, null); + } + + ByteArrayPart(String name, byte[] bytes, ContentType contentType, String fileName, Headers headers) { + super(bytes, name, contentType.toString(), headers); + this.fileName = fileName; + } + + @Override + public String getFileName() { + return fileName; + } + + @Override + public boolean isFile() { + return true; + } + + @Override + public String toString() { + return String.format("%s=%s", getName(), fileName); + } +} diff --git a/unirest/src/main/java/kong/unirest/core/ByteResponse.java b/unirest/src/main/java/kong/unirest/core/ByteResponse.java new file mode 100644 index 000000000..575d01b48 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/ByteResponse.java @@ -0,0 +1,87 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +public class ByteResponse extends BaseResponse { + private final byte[] body; + + public ByteResponse(RawResponse r, ProgressMonitor downloadMonitor) { + super(r); + if(downloadMonitor == null) { + this.body = r.getContentAsBytes(); + } else { + MonitoringInputStream ip = new MonitoringInputStream(r.getContent(), downloadMonitor, (String)null, r); + try { + body = getBytes(ip); + } catch (IOException e){ + throw new UnirestException(e); + } + } + } + + public static byte[] getBytes(InputStream is) throws IOException { + try { + int len; + int size = 1024; + byte[] buf; + + if (is instanceof ByteArrayInputStream) { + size = is.available(); + buf = new byte[size]; + len = is.read(buf, 0, size); + } else { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + buf = new byte[size]; + while ((len = is.read(buf, 0, size)) != -1) { + bos.write(buf, 0, len); + } + buf = bos.toByteArray(); + } + return buf; + } finally { + is.close(); + } + } + + public static boolean isGzipped(String value) { + return "gzip".equalsIgnoreCase(value.toLowerCase().trim()); + } + + @Override + public byte[] getBody() { + return body; + } + + @Override + protected String getRawBody() { + return null; + } +} diff --git a/unirest/src/main/java/kong/unirest/core/Cache.java b/unirest/src/main/java/kong/unirest/core/Cache.java new file mode 100644 index 000000000..1c572d183 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/Cache.java @@ -0,0 +1,180 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.time.Instant; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +/** + * Cache interface for response caching + */ +public interface Cache { + + /** + * Returns the cached HttpResponse for a key or uses the Supplier to fetch the response + * @param key the cache key + * @param fetcher a function to execute the request and return the response. This response should be + * cached by the implementation + * @param the type of response + * @return the Http Response + */ + HttpResponse get(Key key, Supplier> fetcher); + + /** + * Returns the cached HttpResponse for a key or uses the Supplier to fetch the response + * @param key the cache key + * @param fetcher a function to execute the request and return the response. This response should be + * cached by the implementation + * @param the type of response + * @return the CompletableFuture for the response + */ + CompletableFuture getAsync(Key key, Supplier>> fetcher); + + /** + * a builder for cache options + * @return a new Builder. + */ + static Builder builder(){ + return new Builder(); + } + + class Builder { + private int depth = 100; + private long ttl = 0; + private Cache backing; + private KeyGenerator keyGen; + + CacheManager build() { + if(backing != null){ + return new CacheManager(backing, keyGen); + } + return new CacheManager(depth, ttl, keyGen); + } + + /** + * defines the max depth of the cache in number of values. + * defaults to 100. + * Elements exceeding the depth are purged on read. + * Custom Cache implementations may not honor this setting + * @param value the max depth + * @return the current builder. + */ + public Builder depth(int value) { + this.depth = value; + return this; + } + + /** + * Sets a Time-To-Live for response objects. + * There is no TTL by default and objects will be kept indefinitely + * Elements exceeding the TTL are purged on read. + * Custom Cache implementations may not honor this setting + * @param number a number + * @param units the TimeUnits of the number + * @return this builder. + */ + public Builder maxAge(long number, TimeUnit units) { + this.ttl = units.toMillis(number); + return this; + } + + /** + * Sets a custom backing cache. This cache must implement it's own purging rules + * There is no TTL by default and objects will be kept indefinitely + * @param cache the backing cache implementation + * @return this builder. + */ + public Builder backingCache(Cache cache) { + this.backing = cache; + return this; + } + + /** + * Provide a custom key generator. + * The default key is a hash of the request, the request execution type and the response type. + * @param keyGenerator a custom cache key generator + * @return this builder + */ + public Builder withKeyGen(KeyGenerator keyGenerator) { + this.keyGen = keyGenerator; + return this; + } + } + + /** + * A functional interface to generate a cache key + */ + @FunctionalInterface + interface KeyGenerator { + /** + * A function to generate a cache key + * @param request the current http request + * @param isAsync indicates if this request is being executed async + * @param responseType the response type (String, JsonNode, etc) + * @return a key which can be used as a hash for the cache + */ + Key apply(HttpRequest request, Boolean isAsync, Class responseType); + } + + /** + * Interface for the cache key which can be implemented by consumers + * The key should implement equals and hashCode + * It must must return the time the key was created. + */ + interface Key { + /** + * @param obj the reference object with which to compare. + * @return {@code true} if this object is the same as the obj + * argument; {@code false} otherwise. + * @see #hashCode() + * @see java.util.HashMap + */ + @Override + boolean equals(Object obj); + + /** + * As much as is reasonably practical, the hashCode method defined + * by class {@code Object} does return distinct integers for + * distinct objects. (The hashCode may or may not be implemented + * as some function of an object's memory address at some point + * in time.) + * + * @return a hash code value for this object. + * @see java.lang.Object#equals(java.lang.Object) + * @see java.lang.System#identityHashCode + */ + @Override + int hashCode(); + + /** + * The time the key was created to be used by purging functions + * @return the time as an instant + */ + Instant getTime(); + } +} diff --git a/unirest/src/main/java/kong/unirest/core/CacheManager.java b/unirest/src/main/java/kong/unirest/core/CacheManager.java new file mode 100644 index 000000000..20d0b7148 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/CacheManager.java @@ -0,0 +1,196 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import kong.unirest.core.java.Event; + +import java.net.http.WebSocket; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Stream; + +class CacheManager { + + private final CacheWrapper wrapper = new CacheWrapper(); + private final CacheWrapper asyncWrapper = new CacheWrapper(); + private final Cache backingCache; + private final Cache.KeyGenerator keyGen; + + private Client originalClient; + + public CacheManager() { + this(100, 0, HashKey::new); + } + + public CacheManager(int depth, long ttl, Cache.KeyGenerator keyGenerator) { + this(new CacheMap(depth, ttl), keyGenerator); + } + + public CacheManager(Cache backing, Cache.KeyGenerator keyGenerator) { + backingCache = backing; + if(keyGenerator != null){ + this.keyGen = keyGenerator; + }else{ + this.keyGen = HashKey::new; + } + } + + Client wrap(Client client) { + this.originalClient = client; + return wrapper; + } + + Client wrapAsync(Client client) { + this.originalClient = client; + return asyncWrapper; + } + + private Cache.Key getHash(HttpRequest request, Boolean isAsync, Class responseType) { + return keyGen.apply(request, isAsync, responseType); + } + + private static class HashKey implements Cache.Key { + private final int hash; + private final Instant time; + + HashKey(HttpRequest request, Boolean isAsync, Class responseType) { + this(Objects.hash(request.hashCode(), isAsync, responseType), + request.getCreationTime()); + } + + HashKey(int hash, Instant time) { + this.hash = hash; + this.time = time; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + HashKey key = (HashKey) o; + return hash == key.hash; + } + + @Override + public int hashCode() { + return hash; + } + + @Override + public Instant getTime() { + return time; + } + } + + private class CacheWrapper implements Client { + + @Override + public Object getClient() { + return originalClient.getClient(); + } + + @Override + public HttpResponse request(HttpRequest request, + Function> transformer, + Class responseType) { + + Cache.Key hash = getHash(request, false, responseType); + return backingCache.get(hash, + () -> originalClient.request(request, transformer, responseType)); + } + + @Override + public CompletableFuture> request(HttpRequest request, + Function> transformer, + CompletableFuture> callback, + Class responseType) { + Cache.Key key = getHash(request, true, responseType); + return backingCache.getAsync(key, + () -> originalClient.request(request, transformer, callback, responseType)); + } + + @Override // + public WebSocketResponse websocket(WebSocketRequest request, WebSocket.Listener listener) { + return originalClient.websocket(request, listener); + } + + @Override + public CompletableFuture sse(SseRequest request, SseHandler handler) { + return originalClient.sse(request, handler); + } + + @Override + public Stream sse(SseRequest request) { + return Stream.empty(); + } + } + + private static class CacheMap extends LinkedHashMap implements Cache { + private final int maxSize; + private long ttl; + + CacheMap(int maxSize, long ttl) { + this.maxSize = maxSize; + this.ttl = ttl; + } + + @Override + public HttpResponse get(Key key, Supplier> fetcher) { + clearOld(); + return (HttpResponse)super.computeIfAbsent(key, (k) -> fetcher.get()); + } + + @Override + public CompletableFuture getAsync(Key key, Supplier>> fetcher) { + clearOld(); + return (CompletableFuture)super.computeIfAbsent(key, (k) -> fetcher.get()); + } + + private void clearOld() { + if (ttl > 0) { + Instant now = Util.now(); + keySet().removeIf(k -> ChronoUnit.MILLIS.between(k.getTime(), now) > ttl); + } + } + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > maxSize; + } + + } + +} diff --git a/unirest/src/main/java/kong/unirest/core/Callback.java b/unirest/src/main/java/kong/unirest/core/Callback.java new file mode 100644 index 000000000..5c99a85c0 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/Callback.java @@ -0,0 +1,34 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +public interface Callback { + void completed(HttpResponse response); + + default void failed(UnirestException e){} + + default void cancelled(){} +} diff --git a/unirest/src/main/java/kong/unirest/core/CallbackFuture.java b/unirest/src/main/java/kong/unirest/core/CallbackFuture.java new file mode 100644 index 000000000..c063953a2 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/CallbackFuture.java @@ -0,0 +1,51 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.util.concurrent.CompletableFuture; + +class CallbackFuture { + + static CompletableFuture> wrap(Callback source){ + return new CompletableFuture>(){ + @Override + public boolean complete(HttpResponse value) { + source.completed(value); + return super.complete(value); + } + @Override + public boolean completeExceptionally(Throwable ex) { + source.failed(new UnirestException(ex)); + return super.completeExceptionally(ex); + } + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + source.cancelled(); + return super.cancel(mayInterruptIfRunning); + } + }; + } +} \ No newline at end of file diff --git a/unirest/src/main/java/kong/unirest/core/Client.java b/unirest/src/main/java/kong/unirest/core/Client.java new file mode 100644 index 000000000..c73c89bbe --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/Client.java @@ -0,0 +1,138 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import kong.unirest.core.java.Event; + +import java.net.http.WebSocket; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import java.util.stream.Stream; + +/** + * The HTTP client interface responsible for executing HTTP requests. + *

+ * This interface defines the contract for the underlying HTTP client implementation + * that performs the actual network communication. It supports synchronous and asynchronous + * HTTP requests, WebSocket connections, and Server-Sent Events (SSE). + *

+ *

+ * The default implementation uses Java's built-in {@link java.net.http.HttpClient}, + * but this interface allows for alternative implementations or mock clients for testing. + *

+ * + * @see HttpRequest + * @see HttpResponse + * @see RawResponse + */ +public interface Client { + + /** + * Returns the underlying HTTP client instance. + *

+ * This method provides access to the native HTTP client being wrapped by this implementation, + * allowing direct interaction with the underlying library when needed. + *

+ * + * @param the type of the underlying client (e.g., {@link java.net.http.HttpClient}) + * @return the underlying client instance + */ + T getClient(); + + /** + * Executes a synchronous HTTP request and transforms the response. + *

+ * This method blocks until the response is received and transformed. + *

+ * + * @param the type of the transformed response body + * @param request the prepared HTTP request to execute + * @param transformer a function to transform the raw response into the desired type + * @param resultType the expected body result type; used as a hint to downstream systems + * to work around Java type erasure + * @return an {@link HttpResponse} containing the transformed body + */ + HttpResponse request(HttpRequest request, Function> transformer, Class resultType); + + /** + * Executes an asynchronous HTTP request and transforms the response. + *

+ * This method returns immediately with a {@link CompletableFuture} that will be + * completed when the response is received and transformed. + *

+ * + * @param the type of the transformed response body + * @param request the prepared HTTP request to execute + * @param transformer a function to transform the raw response into the desired type + * @param callback the {@link CompletableFuture} that will be completed with the response + * @param resultType the expected body result type; used as a hint to downstream systems + * to work around Java type erasure + * @return a {@link CompletableFuture} that will complete with the transformed {@link HttpResponse} + */ + CompletableFuture> request(HttpRequest request, + Function> transformer, + CompletableFuture> callback, + Class resultType); + + /** + * Establishes a WebSocket connection. + *

+ * Creates a WebSocket connection to the specified endpoint and associates it + * with the provided listener to handle incoming messages and connection events. + *

+ * + * @param request the WebSocket connection request containing the target URI and configuration + * @param listener the {@link WebSocket.Listener} to handle WebSocket events + * @return a {@link WebSocketResponse} representing the established connection + */ + WebSocketResponse websocket(WebSocketRequest request, WebSocket.Listener listener); + + /** + * Establishes a Server-Sent Events (SSE) connection with a handler. + *

+ * Opens an SSE connection to the specified endpoint. Because SSE events are + * streamed continuously, they are processed asynchronously. The provided handler + * is used to consume incoming events as they arrive. + *

+ * + * @param request the SSE request containing the target URI and configuration + * @param handler the {@link SseHandler} to process incoming events + * @return a {@link CompletableFuture} that completes when the SSE connection is closed + */ + CompletableFuture sse(SseRequest request, SseHandler handler); + + /** + * Establishes a Server-Sent Events (SSE) connection and returns a stream of events. + *

+ * Opens an SSE connection to the specified endpoint and returns a {@link Stream} + * of events that can be processed using standard stream operations. + *

+ * + * @param request the SSE request containing the target URI and configuration + * @return a {@link Stream} of {@link Event} objects representing the incoming SSE events + */ + Stream sse(SseRequest request); +} diff --git a/unirest/src/main/java/kong/unirest/core/CompoundInterceptor.java b/unirest/src/main/java/kong/unirest/core/CompoundInterceptor.java new file mode 100644 index 000000000..f1f697da3 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/CompoundInterceptor.java @@ -0,0 +1,76 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.util.*; + +class CompoundInterceptor implements Interceptor { + private List interceptors; + + CompoundInterceptor() { + this(Collections.singletonList(new DefaultInterceptor())); + } + + CompoundInterceptor(List interceptors) { + this.interceptors = interceptors; + } + + @Override + public void onRequest(HttpRequest request, Config config) { + interceptors.forEach(i -> i.onRequest(request, config)); + } + + @Override + public void onResponse(HttpResponse response, HttpRequestSummary request, Config config) { + interceptors.forEach(i -> i.onResponse(response, request, config)); + } + + @Override + public HttpResponse onFail(Exception e, HttpRequestSummary request, Config config) throws UnirestException { + return interceptors.stream() + .map(i -> Optional.ofNullable(i.onFail(e, request, config))) + .flatMap(Util::stream) + .findFirst() + .orElseThrow(() -> new UnirestException(e)); + } + + int size() { + return interceptors.size(); + } + + List getInterceptors() { + return new ArrayList<>(interceptors); + } + + void register(Interceptor t1) { + if(interceptors.stream().anyMatch(i -> i instanceof DefaultInterceptor)){ + interceptors = new ArrayList<>(); + } + if(!interceptors.contains(t1)){ + interceptors.add(t1); + } + } +} diff --git a/unirest/src/main/java/kong/unirest/core/Config.java b/unirest/src/main/java/kong/unirest/core/Config.java new file mode 100644 index 000000000..f4b1ffc12 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/Config.java @@ -0,0 +1,1226 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import kong.unirest.core.java.JavaClient; +import kong.unirest.core.json.CoreFactory; + +import javax.net.ssl.SSLContext; +import java.io.InputStream; +import java.net.Authenticator; +import java.net.ProxySelector; +import java.net.http.*; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.security.KeyStore; +import java.time.Duration; +import java.util.*; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * Central configuration class for Unirest HTTP client settings. + *

+ * This class provides a fluent API for configuring all aspects of HTTP client behavior, + * including timeouts, proxies, SSL/TLS settings, headers, cookies, interceptors, and more. + * Configuration can be applied to the primary static {@link Unirest} instance or to + * individual {@link UnirestInstance} objects. + *

+ * + *

Basic Usage:

+ *
{@code
+ * // Configure the primary instance
+ * Unirest.config()
+ *     .connectTimeout(5000)
+ *     .defaultBaseUrl("https://api.example.com")
+ *     .setDefaultHeader("Accept", "application/json");
+ *
+ * // Or configure a custom instance
+ * UnirestInstance instance = new UnirestInstance(new Config());
+ * instance.config()
+ *     .proxy("proxy.example.com", 8080)
+ *     .verifySsl(false);
+ * }
+ * + *

Configuration Categories:

+ *
    + *
  • Timeouts: {@link #connectTimeout(int)}, {@link #requestTimeout(Integer)}, {@link #connectionTTL(long, TimeUnit)}
  • + *
  • Proxy: {@link #proxy(String, int)}, {@link #proxy(Proxy)}, {@link #proxy(ProxySelector)}
  • + *
  • SSL/TLS: {@link #verifySsl(boolean)}, {@link #sslContext(SSLContext)}, {@link #clientCertificateStore(KeyStore, String)}
  • + *
  • Headers/Cookies: {@link #setDefaultHeader(String, String)}, {@link #addDefaultCookie(String, String)}
  • + *
  • Serialization: {@link #setObjectMapper(ObjectMapper)}
  • + *
  • Behavior: {@link #followRedirects(boolean)}, {@link #enableCookieManagement(boolean)}, {@link #retryAfter(boolean)}
  • + *
  • Caching: {@link #cacheResponses(boolean)}, {@link #cacheResponses(Cache.Builder)}
  • + *
  • Interceptors: {@link #interceptor(Interceptor)}, {@link #instrumentWith(UniMetric)}
  • + *
+ * + *

Thread Safety:

+ *

+ * Configuration should be performed before making requests. Once HTTP clients are built + * (after the first request), certain settings cannot be changed without calling {@link #reset()}. + * Methods that require this will throw {@link UnirestConfigException} if clients are already running. + *

+ * + * @see Unirest + * @see UnirestInstance + * @see Client + * @see Interceptor + */ +public class Config { + /** + * Default connection timeout in milliseconds (10 seconds). + * Used when no custom timeout is configured via {@link #connectTimeout(int)}. + */ + public static final int DEFAULT_CONNECT_TIMEOUT = 10000; + + /** + * System property name for configuring HTTP keep-alive timeout. + *

+ * This property controls the number of seconds to keep idle HTTP connections + * alive in the keep-alive cache. Applies to both HTTP/1.1 and HTTP/2. + *

+ * + * @see #connectionTTL(long, TimeUnit) + * @see Java HTTP Client Module + */ + public static final String JDK_HTTPCLIENT_KEEPALIVE_TIMEOUT = "jdk.httpclient.keepalive.timeout"; + + /** + * System property name for disabling hostname verification. + *

+ * When set to "true", disables SSL hostname verification for the entire JVM. + * Warning: This affects all HTTP clients in the JVM, not just Unirest. + * Should only be used in non-production environments. + *

+ * + * @see #disableHostNameVerification(boolean) + */ + public static final String JDK_HTTPCLIENT_DISABLE_HOST_NAME_VERIFICATION = "jdk.internal.httpclient.disableHostnameVerification"; + + private Optional client = Optional.empty(); + private Supplier objectMapper; + + private Executor customExecutor; + private Headers headers; + private Proxy proxy; + private int connectionTimeout; + private Integer requestTimeout; + private boolean followRedirects; + private boolean cookieManagement; + private boolean useSystemProperties; + private String defaultResponseEncoding = StandardCharsets.UTF_8.name(); + private Function clientBuilder; + private boolean requestCompressionOn = true; + private boolean verifySsl = true; + private KeyStore keystore; + private Supplier keystorePassword = () -> null; + private String cookieSpec; + private UniMetric metrics = new NoopMetric(); + private SSLContext sslContext; + private String[] ciphers; + private String[] protocols; + private CompoundInterceptor interceptor = new CompoundInterceptor(); + private String defaultBaseUrl; + private CacheManager cache; + private HttpClient.Version version = HttpClient.Version.HTTP_2; + private RetryStrategy retry; + private Authenticator authenticator; + private ProxySelector proxySelector; + + /** + * Creates a new Config instance with default settings. + */ + public Config() { + setDefaults(); + } + + private void setDefaults() { + proxy = null; + cache = null; + customExecutor = null; + headers = new Headers(); + connectionTimeout = DEFAULT_CONNECT_TIMEOUT; + requestTimeout = null; + followRedirects = true; + useSystemProperties = false; + cookieManagement = true; + requestCompressionOn = true; + verifySsl = true; + keystore = null; + keystorePassword = null; + sslContext = null; + ciphers = null; + protocols = null; + defaultBaseUrl = null; + interceptor = new CompoundInterceptor(); + retry = null; + objectMapper = () -> CoreFactory.getCore().getObjectMapper(); + version = HttpClient.Version.HTTP_2; + authenticator = null; + proxySelector = null; + + try { + clientBuilder = JavaClient::new; + }catch (BootstrapMethodError e){ + throw new UnirestException("It looks like you are using an older version of Apache Http Client. \n" + + "For security and performance reasons Unirest requires the most recent version. Please upgrade.", e); + } + } + + /** + * Set the HttpClient implementation to use for every synchronous request + * + * @param httpClient Custom httpClient implementation + * @return this config object + */ + public Config httpClient(Client httpClient) { + client = Optional.ofNullable(httpClient); + return this; + } + + /** + * Provide a builder for a client + * + * @param httpClient Custom httpClient implementation + * @return this config object + */ + public Config httpClient(Function httpClient) { + clientBuilder = httpClient; + return this; + } + + /** + * Sets a custom executor for requests + * @param executor – the Executor + * @return this config builder + * Implementation Note: + * The default executor uses a thread pool, with a custom thread factory. + * If a security manager has been installed, the thread factory creates + * threads that run with an access control context that has no permissions. + */ + public Config executor(Executor executor){ + this.customExecutor = executor; + return this; + } + + /** + * Set a proxy + * This will set any ProxySelector object already set to null and take over all proxy work + * + * @param value Proxy settings object. + * @return this config object + */ + public Config proxy(Proxy value) { + validateClientsNotRunning(); + this.proxySelector = null; + this.proxy = value; + return this; + } + + /** + * Set a proxy selector. + * This will set any Proxy object already set to null and take over all proxy work + * + * @param value ProxySelector object. + * @return this config object + */ + public Config proxy(ProxySelector value) { + validateClientsNotRunning(); + this.proxy = null; + this.proxySelector = value; + return this; + } + + /** + * Set a proxy + * + * @param host the hostname of the proxy server. + * @param port the port of the proxy server + * @return this config object + */ + public Config proxy(String host, int port) { + return proxy(new Proxy(host, port)); + } + + /** + * Set an authenticated proxy + * + * @param host the hostname of the proxy server. + * @param port the port of the proxy server + * @param username username for authenticated proxy + * @param password password for authenticated proxy + * @return this config object + */ + public Config proxy(String host, int port, String username, String password) { + return proxy(new Proxy(host, port, username, password)); + } + + /** + * Set the ObjectMapper implementation to use for Response to Object binding + * + * @param om Custom implementation of ObjectMapper interface + * @return this config object + */ + public Config setObjectMapper(ObjectMapper om) { + this.objectMapper = () -> om; + return this; + } + + /** + * Set a custom SSLContext. + * + * @param ssl the SSLContext to use for custom ssl context + * @return this config object + * @throws UnirestConfigException if a keystore was already configured. + */ + public Config sslContext(SSLContext ssl) { + verifySecurityConfig(this.keystore); + this.sslContext = ssl; + return this; + } + + /** + * Set a custom array of ciphers + * @param values the array of ciphers + * @return this config object + */ + public Config ciphers(String... values) { + this.ciphers = values; + return this; + } + + /** + * Set a custom array of protocols + * @param values the array of protocols + * @return this config object + */ + public Config protocols(String... values) { + this.protocols = values; + return this; + } + + private void verifySecurityConfig(Object thing) { + if(thing != null){ + throw new UnirestConfigException("You may only configure a SSLContext OR a Keystore, but not both"); + } + } + + /** + * Set a custom keystore + * + * @param store the keystore to use for a custom ssl context + * @param password the password for the store + * @return this config object + * @throws UnirestConfigException if a SSLContext was already configured. + */ + public Config clientCertificateStore(KeyStore store, String password) { + verifySecurityConfig(this.sslContext); + this.keystore = store; + this.keystorePassword = () -> password; + return this; + } + + /** + * Set a custom keystore via a file path. Must be a valid PKCS12 file + * + * @param fileLocation the path keystore to use for a custom ssl context + * @param password the password for the store + * @return this config object + * @throws UnirestConfigException if a SSLContext was already configured. + */ + public Config clientCertificateStore(String fileLocation, String password) { + verifySecurityConfig(this.sslContext); + try (InputStream keyStoreStream = Util.getFileInputStream(fileLocation)) { + this.keystorePassword = () -> password; + this.keystore = KeyStore.getInstance("PKCS12"); + this.keystore.load(keyStoreStream, keystorePassword.get().toCharArray()); + } catch (Exception e) { + throw new UnirestConfigException(e); + } + return this; + } + + /** + * Sets the connect timeout duration for this client. + * + *

In the case where a new connection needs to be established, if + * the connection cannot be established within the given {@code + * duration}, then {@link HttpClient#send(java.net.http.HttpRequest, HttpResponse.BodyHandler) + * HttpClient::send} throws an {@link HttpConnectTimeoutException}, or + * {@link HttpClient#sendAsync(HttpRequest, HttpResponse.BodyHandler) + * HttpClient::sendAsync} completes exceptionally with an + * {@code HttpConnectTimeoutException}. If a new connection does not + * need to be established, for example if a connection can be reused + * from a previous request, then this timeout duration has no effect. + * + * @param inMillies the duration to allow the underlying connection to be + * established + * @return this builder + */ + public Config connectTimeout(int inMillies) { + validateClientsNotRunning(); + this.connectionTimeout = inMillies; + return this; + } + + /** + * Sets a default timeout for all requests. If the response is not received + * within the specified timeout then an {@link HttpTimeoutException} is + * thrown. + * completes exceptionally with an {@code HttpTimeoutException}. The effect + * of not setting a timeout is the same as setting an infinite Duration, ie. + * block forever. + * + * @param inMillies the timeout duration in millies + * @return this builder + * @throws IllegalArgumentException if the duration is non-positive + */ + public Config requestTimeout(Integer inMillies){ + if(inMillies != null && inMillies < 1){ + throw new IllegalArgumentException("request timeout must be a positive integer"); + } + this.requestTimeout = inMillies; + return this; + } + + /** + * Removes all default headers from this configuration. + * + * @return this config object for method chaining + * @see #setDefaultHeader(String, String) + * @see #addDefaultHeader(String, String) + */ + public Config clearDefaultHeaders() { + headers.clear(); + return this; + } + + /** + * Sets default Basic Authentication credentials for all requests. + *

+ * This adds an {@code Authorization} header with Base64-encoded credentials + * to all requests made with this configuration. + *

+ * + * @param username the username for Basic Authentication + * @param password the password for Basic Authentication + * @return this config object for method chaining + */ + public Config setDefaultBasicAuth(String username, String password) { + headers.replace("Authorization", Util.toBasicAuthValue(username, password)); + return this; + } + + /** + * Set default header to appear on all requests + * + * @param name The name of the header. + * @param value The value of the header. + * @return this config object + */ + public Config setDefaultHeader(String name, String value) { + headers.replace(name, value); + return this; + } + + /** + * Set default header to appear on all requests, value is through a Supplier + * This is useful for adding tracing elements to requests. + * + * @param name The name of the header. + * @param value a supplier that will get called as part of the request. + * @return this config object + */ + public Config setDefaultHeader(String name, Supplier value) { + headers.add(name, value); + return this; + } + + /** + * Add default header to appear on all requests + * + * @param name The name of the header. + * @param value The value of the header. + * @return this config object + */ + public Config addDefaultHeader(String name, String value) { + headers.add(name, value); + return this; + } + + /** + * Adds a default cookie to be added to all requests with this config + * @param name the name of the cookie + * @param value the value of the cookie + * @return this config object + */ + public Config addDefaultCookie(String name, String value) { + return addDefaultCookie(new Cookie(name, value)); + } + + /** + * Adds a default cookie to be added to all requests with this config + * @param cookie the cookie + * @return this config object + */ + public Config addDefaultCookie(Cookie cookie) { + this.headers.cookie(cookie); + return this; + } + + /** + * Add a metric object for instrumentation + * @param metric a UniMetric object + * @return this config object + */ + public Config instrumentWith(UniMetric metric) { + this.metrics = metric; + return this; + } + + /** + * Add a Interceptor which will be called before and after the request; + * @param value The Interceptor + * @return this config object + */ + public Config interceptor(Interceptor value) { + Objects.requireNonNull(value, "Interceptor may not be null"); + this.interceptor.register(value); + return this; + } + + /** + * Configures whether the client should automatically follow redirects. + *

+ * When enabled, the client will automatically follow HTTP redirects (3xx responses). + *

+ * + * @param enable {@code true} to follow redirects (default), {@code false} to disable + * @return this config object for method chaining + * @throws UnirestConfigException if called after the client has been built + * @see #getFollowRedirects() + */ + public Config followRedirects(boolean enable) { + validateClientsNotRunning(); + this.followRedirects = enable; + return this; + } + + /** + * Configures whether the client should manage cookies automatically. + *

+ * When enabled, the client will store cookies from responses and send them + * with subsequent requests to the same domain. + *

+ * + * @param enable {@code true} to enable cookie management (default), {@code false} to disable + * @return this config object for method chaining + * @throws UnirestConfigException if called after the client has been built + * @see #getEnabledCookieManagement() + */ + public Config enableCookieManagement(boolean enable) { + validateClientsNotRunning(); + this.cookieManagement = enable; + return this; + } + + /** + * Configures whether SSL/TLS certificates should be verified. + *

+ * Warning: Disabling SSL verification is a security risk and should + * only be done in development or testing environments, never in production. + *

+ * + * @param value {@code true} to verify SSL certificates (default), {@code false} to skip verification + * @return this config object for method chaining + * @see #isVerifySsl() + */ + public Config verifySsl(boolean value) { + this.verifySsl = value; + return this; + } + + /** + * Configures whether the client should use system properties for configuration. + *

+ * When enabled, the client will read proxy settings and other configuration + * from system properties. + *

+ * + * @param value {@code true} to use system properties, {@code false} to ignore them (default) + * @return this config object for method chaining + * @see #useSystemProperties() + */ + public Config useSystemProperties(boolean value) { + this.useSystemProperties = value; + return this; + } + + /** + * Configures whether the client should request compressed responses. + *

+ * When enabled, the client sends an {@code Accept-Encoding: gzip} header + * and automatically decompresses GZIP-encoded responses. + *

+ * + * @param value {@code true} to request compression (default), {@code false} to disable + * @return this config object for method chaining + * @see #isRequestCompressionOn() + */ + public Config requestCompression(boolean value) { + this.requestCompressionOn = value; + return this; + } + + /** + * Sets the cookie specification policy for cookie handling. + *

+ * Acceptable values: + *

    + *
  • {@code "default"} - same as Netscape
  • + *
  • {@code "netscape"} - original Netscape cookie spec
  • + *
  • {@code "ignoreCookies"} - ignore all cookies
  • + *
  • {@code "standard"} - RFC 6265 interoperability profile
  • + *
  • {@code "standard-strict"} - RFC 6265 strict profile
  • + *
+ * + * @param policy the cookie policy name + * @return this config object for method chaining + * @see #getCookieSpec() + */ + public Config cookieSpec(String policy) { + this.cookieSpec = policy; + return this; + } + + /** + * Enables or disables response caching with default options. + *

+ * When enabled, HTTP responses will be cached based on standard HTTP caching headers. + *

+ * + * @param value {@code true} to enable caching, {@code false} to disable + * @return this config object for method chaining + */ + public Config cacheResponses(boolean value) { + if(value){ + this.cache = new CacheManager(); + } else { + this.cache = null; + } + return this; + } + + /** + * Enables response caching with custom cache configuration. + * + * @param value the cache builder with custom configuration + * @return this config object for method chaining + * @see Cache.Builder + */ + public Config cacheResponses(Cache.Builder value) { + this.cache = value.build(); + return this; + } + + /** + * Sets the default character encoding for response body serialization. + * + * @param value the encoding name (e.g., "UTF-8", "ISO-8859-1") + * @return this config object for method chaining + * @throws NullPointerException if value is null + * @see #getDefaultResponseEncoding() + */ + public Config setDefaultResponseEncoding(String value) { + Objects.requireNonNull(value, "Encoding cannot be null"); + this.defaultResponseEncoding = value; + return this; + } + + /** + * Sets the HTTP connection keep-alive timeout. + *

+ * This configures the {@code jdk.httpclient.keepalive.timeout} system property, + * which controls how long idle HTTP connections are kept alive in the connection pool. + * Applies to both HTTP/1.1 and HTTP/2. + *

+ * + * @param duration the keep-alive duration + * @param unit the time unit of the duration + * @return this config object for method chaining + * @throws NullPointerException if unit is null + * @see #getTTL() + * @see Java HTTP Client Module + */ + public Config connectionTTL(long duration, TimeUnit unit) { + Objects.requireNonNull(unit, "TimeUnit required"); + var ttl = unit.toSeconds(duration); + if(ttl > -1){ + System.setProperty(JDK_HTTPCLIENT_KEEPALIVE_TIMEOUT, String.valueOf(ttl)); + } + return this; + } + + /** + * Sets the HTTP connection keep-alive timeout. + *

+ * This configures the {@code jdk.httpclient.keepalive.timeout} system property, + * which controls how long idle HTTP connections are kept alive in the connection pool. + * Applies to both HTTP/1.1 and HTTP/2. + *

+ * + * @param duration the keep-alive duration + * @return this config object for method chaining + * @see #getTTL() + * @see Java HTTP Client Module + */ + public Config connectionTTL(Duration duration){ + return connectionTTL(duration.toMillis(), TimeUnit.MILLISECONDS); + } + + /** + * Enables automatic retry for requests that receive 429 (Too Many Requests) or + * 529 (Site Overloaded) responses with a {@code Retry-After} header. + *

+ * When enabled, the client will wait for the duration specified in the + * {@code Retry-After} header before automatically retrying the request, + * up to a maximum of 10 retry attempts. + *

+ * + * @param value {@code true} to enable automatic retry, {@code false} to disable (default) + * @return this config object for method chaining + * @see #retryAfter(boolean, int) + * @see #isAutomaticRetryAfter() + */ + public Config retryAfter(boolean value) { + return retryAfter(value, 10); + } + + /** + * Enables automatic retry for requests that receive 429 (Too Many Requests) or + * 529 (Site Overloaded) responses with a {@code Retry-After} header. + *

+ * When enabled, the client will wait for the duration specified in the + * {@code Retry-After} header before automatically retrying the request. + *

+ * + * @param value {@code true} to enable automatic retry, {@code false} to disable (default) + * @param maxRetryAttempts the maximum number of retry attempts + * @return this config object for method chaining + * @see #maxRetries() + * @see #isAutomaticRetryAfter() + */ + public Config retryAfter(boolean value, int maxRetryAttempts) { + if(value) { + this.retry = new RetryStrategy.Standard(maxRetryAttempts); + } else { + this.retry = null; + } + return this; + } + + /** + * Configures automatic retry using a custom retry strategy. + *

+ * This allows for custom logic to determine when and how to retry requests. + *

+ * + * @param strategy the custom {@link RetryStrategy} to use, or {@code null} to disable retry + * @return this config object for method chaining + * @see #getRetryStrategy() + * @see #isAutomaticRetryAfter() + */ + public Config retryAfter(RetryStrategy strategy) { + this.retry = strategy; + return this; + } + + /** + * Requests a specific HTTP protocol version where possible. + *

+ * If this method is not invoked, newly built clients will prefer + * {@link HttpClient.Version#HTTP_2 HTTP/2}. + *

+ *

+ * If set to {@link HttpClient.Version#HTTP_2 HTTP/2}, each request will attempt + * to upgrade to HTTP/2. If the upgrade succeeds, the response and all subsequent + * requests/responses to the same origin server will use HTTP/2. If the upgrade + * fails, HTTP/1.1 will be used. + *

+ *

+ * Constraints may affect protocol version selection. For example, if HTTP/2 is + * requested through a proxy and the implementation does not support this mode, + * HTTP/1.1 may be used instead. + *

+ * + * @param value the requested HTTP protocol version + * @return this config object for method chaining + * @throws NullPointerException if value is null + * @see #getVersion() + */ + public Config version(HttpClient.Version value) { + Objects.requireNonNull(value); + version = value; + return this; + } + + /** + * Sets a default base URL to be prepended to all relative request paths. + *

+ * This is useful when making multiple requests to the same server. The base URL + * is only used if the request path does not already contain a valid base URL. + * The URL may contain path parameters. + *

+ * + *

Example:

+ *
{@code
+     * Unirest.config().defaultBaseUrl("http://api.example.com");
+     * Unirest.get("/users");  // Results in: http://api.example.com/users
+     * }
+ * + * @param value the base URL to use for relative paths + * @return this config object for method chaining + * @see #getDefaultBaseUrl() + */ + public Config defaultBaseUrl(String value) { + this.defaultBaseUrl = value; + return this; + } + + /** + * Returns the default headers that are added to every request. + * + * @return the default {@link Headers} collection + */ + public Headers getDefaultHeaders() { + return headers; + } + + /** + * Checks if the HTTP client has been built and is currently running. + *

+ * Once a client is running, certain configuration options cannot be changed + * without first calling {@link #reset()}. + *

+ * + * @return {@code true} if the client has been built and is running, {@code false} otherwise + */ + public boolean isRunning() { + return client.isPresent(); + } + + /** + * Shuts down the current HTTP client and prepares for re-initialization. + *

+ * This method preserves all configuration settings but releases the HTTP client, + * allowing configuration changes that require a client restart. + *

+ * + * @return this config object for method chaining + * @see #reset(boolean) + */ + public Config reset() { + reset(false); + return this; + } + + + /** + * Shuts down the HTTP client and optionally resets all configuration to defaults. + *

+ * When {@code clearOptions} is {@code false}, all configuration settings are preserved + * but the HTTP client is released. When {@code true}, all settings are reset to their + * default values. + *

+ * + * @param clearOptions if {@code true}, reset all settings to defaults; if {@code false}, preserve current settings + */ + public void reset(boolean clearOptions) { + client = Optional.empty(); + + if (clearOptions) { + setDefaults(); + } + } + + /** + * Returns the current HTTP client, building one if it does not yet exist. + *

+ * If response caching is enabled, the returned client will be wrapped + * with caching functionality. + *

+ * + * @return the configured {@link Client} instance + */ + public Client getClient() { + if (!client.isPresent()) { + buildClient(); + } + return getFinalClient(); + } + + private Client getFinalClient(){ + if(cache == null){ + return client.get(); + } else { + return cache.wrap(client.get()); + } + } + + private synchronized void buildClient() { + if (!client.isPresent()) { + client = Optional.of(clientBuilder.apply(this)); + } + } + + /** + * Returns whether cookie management is enabled. + * + * @return {@code true} if cookie management is enabled (default), {@code false} otherwise + * @see #enableCookieManagement(boolean) + */ + public boolean getEnabledCookieManagement() { + return cookieManagement; + } + + /** + * Returns whether automatic redirect following is enabled. + * + * @return {@code true} if redirects are followed automatically (default), {@code false} otherwise + * @see #followRedirects(boolean) + */ + public boolean getFollowRedirects() { + return followRedirects; + } + + /** + * Returns the connection timeout in milliseconds. + * + * @return the connection timeout in milliseconds (default: {@value #DEFAULT_CONNECT_TIMEOUT}) + * @see #connectTimeout(int) + */ + public int getConnectionTimeout() { + return connectionTimeout; + } + + /** + * Returns the request timeout in milliseconds. + * + * @return the request timeout in milliseconds, or {@code null} for infinite timeout (default) + * @see #requestTimeout(Integer) + */ + public Integer getRequestTimeout() { + return requestTimeout; + } + + /** + * Returns the configured SSL keystore for client certificate authentication. + * + * @return the configured {@link KeyStore}, or {@code null} if not configured + * @see #clientCertificateStore(KeyStore, String) + * @see #clientCertificateStore(String, String) + */ + public KeyStore getKeystore() { + return this.keystore; + } + + /** + * Returns the password for the configured keystore. + * + * @return the keystore password, or {@code null} if not configured + * @see #clientCertificateStore(KeyStore, String) + */ + public String getKeyStorePassword() { + return this.keystorePassword.get(); + } + + /** + * Returns the configured ObjectMapper for JSON serialization/deserialization. + * + * @return the configured {@link ObjectMapper} + * @throws UnirestConfigException if no ObjectMapper has been configured + * @see #setObjectMapper(ObjectMapper) + */ + public ObjectMapper getObjectMapper() { + ObjectMapper om = this.objectMapper.get(); + if(om == null){ + throw new UnirestConfigException("No Object Mapper Configured. Please config one with Unirest.config().setObjectMapper"); + } + return om; + } + + private void validateClientsNotRunning() { + if (client.isPresent()) { + throw new UnirestConfigException( + "Http Clients are already built in order to build a new config execute Unirest.config().reset() before changing settings. \n" + + "This should be done rarely." + ); + } + } + + /** + * Returns the configured proxy settings. + * + * @return the configured {@link Proxy}, or {@code null} if no proxy is configured + * @see #proxy(Proxy) + * @see #proxy(String, int) + */ + public Proxy getProxy() { + return proxy; + } + + /** + * Returns whether the client uses system properties for configuration. + * + * @return {@code true} if system properties are used, {@code false} otherwise (default) + * @see #useSystemProperties(boolean) + */ + public boolean useSystemProperties() { + return this.useSystemProperties; + } + + /** + * Returns the default character encoding for response body serialization. + * + * @return the default encoding name (default: UTF-8) + * @see #setDefaultResponseEncoding(String) + */ + public String getDefaultResponseEncoding() { + return defaultResponseEncoding; + } + + /** + * Returns whether request compression (GZIP) is enabled. + * + * @return {@code true} if compression is enabled (default), {@code false} otherwise + * @see #requestCompression(boolean) + */ + public boolean isRequestCompressionOn() { + return requestCompressionOn; + } + + /** + * Will unirest verify the SSL? + * You should only do this in non-prod environments. + * Default is true + * @return if unirest will verify the SSL + */ + public boolean isVerifySsl() { + return verifySsl; + } + + /** + * Returns the configured cookie specification policy. + * + * @return the cookie spec name, or {@code null} if using default behavior + * @see #cookieSpec(String) + */ + public String getCookieSpec() { + return cookieSpec; + } + + /** + * Returns the configured metrics collector for instrumentation. + * + * @return the configured {@link UniMetric} instance + * @see #instrumentWith(UniMetric) + */ + public UniMetric getMetric() { + return metrics; + } + + /** + * Returns the configured request/response interceptor. + * + * @return the configured {@link Interceptor} instance + * @see #interceptor(Interceptor) + */ + public Interceptor getUniInterceptor() { + return interceptor; + } + + /** + * Returns the configured SSL context for secure connections. + * + * @return the configured {@link SSLContext}, or {@code null} if not configured + * @see #sslContext(SSLContext) + */ + public SSLContext getSslContext() { + return sslContext; + } + + /** + * Returns the configured SSL/TLS cipher suites. + * + * @return the array of cipher suite names, or {@code null} if using defaults + * @see #ciphers(String...) + */ + public String[] getCiphers() { + return ciphers; + } + + /** + * Returns the configured SSL/TLS protocols. + * + * @return the array of protocol names, or {@code null} if using defaults + * @see #protocols(String...) + */ + public String[] getProtocols() { + return protocols; + } + + /** + * Returns the default base URL prepended to relative request paths. + * + * @return the default base URL, or {@code null} if not configured + * @see #defaultBaseUrl(String) + */ + public String getDefaultBaseUrl() { + return this.defaultBaseUrl; + } + + /** + * Returns the custom executor used for asynchronous requests. + * + * @return the custom {@link Executor}, or {@code null} if using the default + * @see #executor(Executor) + */ + public Executor getCustomExecutor(){ + return customExecutor; + } + + /** + * Returns the preferred HTTP protocol version. + * + * @return the configured {@link HttpClient.Version} (default: HTTP/2) + * @see #version(HttpClient.Version) + */ + public HttpClient.Version getVersion() { + return version; + } + + /** + * Returns whether automatic retry on 429/529 responses is enabled. + * + * @return {@code true} if automatic retry is enabled, {@code false} otherwise (default) + * @see #retryAfter(boolean) + * @see #retryAfter(RetryStrategy) + */ + public boolean isAutomaticRetryAfter(){ + return retry != null; + } + + /** + * Returns the maximum number of retry attempts for 429/529 responses. + * + * @return the maximum number of retry attempts + * @throws NullPointerException if no retry strategy is configured + * @see #retryAfter(boolean, int) + */ + public int maxRetries() { + return retry.getMaxAttempts(); + } + + /** + * Returns the connection keep-alive timeout (TTL) in seconds. + *

+ * This value is read from the system property {@value #JDK_HTTPCLIENT_KEEPALIVE_TIMEOUT}. + *

+ * + * @return the TTL in seconds, or {@code -1} if not configured or invalid + * @see #connectionTTL(long, TimeUnit) + */ + public long getTTL() { + try { + return Long.parseLong(System.getProperty(JDK_HTTPCLIENT_KEEPALIVE_TIMEOUT)); + }catch (NumberFormatException e){ + return -1; + } + } + + /** + * Returns the configured retry strategy for handling 429/529 responses. + * + * @return the configured {@link RetryStrategy}, or {@code null} if not configured + * @see #retryAfter(RetryStrategy) + */ + public RetryStrategy getRetryStrategy() { + return retry; + } + + /** + * Sets the system property jdk.internal.httpclient.disableHostnameVerification + * + * Disables or enables HostNameVerification for the ENTIRE JVM. This will impact all consumers of + * Unirest, java.net.http.HttpClient, or other consumers of either. + * + * @param enabled boolean value for property. true DISABLES host name verification + * @return this config object + */ + public Config disableHostNameVerification(boolean enabled) { + System.setProperty(JDK_HTTPCLIENT_DISABLE_HOST_NAME_VERIFICATION, String.valueOf(enabled)); + return this; + } + + /** + * Sets an authenticator for handling authentication challenges. + *

+ * The authenticator is used by the underlying HTTP client to respond to + * authentication challenges from servers or proxies. + *

+ * + * @param auth the {@link Authenticator} to use for authentication challenges + * @return this config object for method chaining + * @see #getAuthenticator() + */ + public Config authenticator(Authenticator auth) { + this.authenticator = auth; + return this; + } + + /** + * Returns the configured authenticator for handling authentication challenges. + * + * @return the configured {@link Authenticator}, or {@code null} if not configured + * @see #authenticator(Authenticator) + */ + public Authenticator getAuthenticator(){ + return authenticator; + } + + /** + * Returns the configured proxy selector for dynamic proxy selection. + * + * @return the configured {@link ProxySelector}, or {@code null} if not configured + * @see #proxy(ProxySelector) + */ + public ProxySelector getProxySelector(){ + return proxySelector; + } +} diff --git a/unirest/src/main/java/kong/unirest/core/ContentType.java b/unirest/src/main/java/kong/unirest/core/ContentType.java new file mode 100644 index 000000000..40859c220 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/ContentType.java @@ -0,0 +1,260 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.HashSet; +import java.util.Set; + +import static java.nio.charset.StandardCharsets.ISO_8859_1; + +/** + * Represents an HTTP Content-Type header value, consisting of a MIME type and optional character encoding. + *

+ * This class provides constants for commonly used content types and factory methods for creating + * custom content types. It also tracks binary content types for proper handling of response bodies. + * + *

Usage Examples:

+ *
{@code
+ * // Using predefined constants
+ * Unirest.post(url)
+ *     .contentType(ContentType.APPLICATION_JSON)
+ *     .body(jsonString)
+ *     .asString();
+ *
+ * // Creating a custom content type
+ * ContentType custom = ContentType.create("application/vnd.api+json", StandardCharsets.UTF_8);
+ *
+ * // Creating a content type with a different charset
+ * ContentType xmlUtf8 = ContentType.APPLICATION_XML.withCharset(StandardCharsets.UTF_8);
+ * }
+ * + * @see IANA Media Types + */ +public class ContentType { + private static final Set BINARY_TYPES = new HashSet<>(); + + /** {@code application/atom+xml} with ISO-8859-1 charset. */ + public static final ContentType APPLICATION_ATOM_XML = create("application/atom+xml", ISO_8859_1); + + /** {@code application/x-www-form-urlencoded} with ISO-8859-1 charset. Used for HTML form submissions. */ + public static final ContentType APPLICATION_FORM_URLENCODED = create("application/x-www-form-urlencoded", ISO_8859_1); + + /** {@code application/json} with UTF-8 charset. Used for JSON data. */ + public static final ContentType APPLICATION_JSON = create("application/json", StandardCharsets.UTF_8); + + /** {@code application/json-patch+json} for JSON Patch documents (RFC 6902). */ + public static final ContentType APPLICATION_JSON_PATCH = create("application/json-patch+json"); + + /** {@code application/octet-stream} for arbitrary binary data. Marked as binary type. */ + public static final ContentType APPLICATION_OCTET_STREAM = create("application/octet-stream", true); + + /** {@code binary/octet-stream} for binary data. Marked as binary type. */ + public static final ContentType BINARY_OCTET_STREAM = create("binary/octet-stream", true); + + /** {@code application/svg+xml} with ISO-8859-1 charset. Used for SVG images in XML format. */ + public static final ContentType APPLICATION_SVG_XML = create("application/svg+xml", ISO_8859_1); + + /** {@code application/xhtml+xml} with ISO-8859-1 charset. Used for XHTML documents. */ + public static final ContentType APPLICATION_XHTML_XML = create("application/xhtml+xml", ISO_8859_1); + + /** {@code application/xml} with ISO-8859-1 charset. Used for XML data. */ + public static final ContentType APPLICATION_XML = create("application/xml", ISO_8859_1); + + /** {@code application/pdf} for PDF documents. Marked as binary type. */ + public static final ContentType APPLICATION_PDF = create("application/pdf", true); + + /** {@code image/bmp} for BMP images. Marked as binary type. */ + public static final ContentType IMAGE_BMP = create("image/bmp", true); + + /** {@code image/gif} for GIF images. Marked as binary type. */ + public static final ContentType IMAGE_GIF = create("image/gif", true); + + /** {@code image/jpeg} for JPEG images. Marked as binary type. */ + public static final ContentType IMAGE_JPEG = create("image/jpeg", true); + + /** {@code image/png} for PNG images. Marked as binary type. */ + public static final ContentType IMAGE_PNG = create("image/png", true); + + /** {@code image/svg+xml} for SVG images. */ + public static final ContentType IMAGE_SVG = create("image/svg+xml"); + + /** {@code image/tiff} for TIFF images. Marked as binary type. */ + public static final ContentType IMAGE_TIFF = create("image/tiff", true); + + /** {@code image/webp} for WebP images. Marked as binary type. */ + public static final ContentType IMAGE_WEBP = create("image/webp", true); + + /** {@code multipart/form-data} with ISO-8859-1 charset. Used for file uploads and form data with binary content. */ + public static final ContentType MULTIPART_FORM_DATA = create("multipart/form-data", ISO_8859_1); + + /** {@code text/html} with ISO-8859-1 charset. Used for HTML documents. */ + public static final ContentType TEXT_HTML = create("text/html", ISO_8859_1); + + /** {@code text/plain} with ISO-8859-1 charset. Used for plain text content. */ + public static final ContentType TEXT_PLAIN = create("text/plain", ISO_8859_1); + + /** {@code text/xml} with ISO-8859-1 charset. Used for XML content. */ + public static final ContentType TEXT_XML = create("text/xml", ISO_8859_1); + + /** {@code text/event-stream} with UTF-8 charset. Used for Server-Sent Events (SSE). */ + public static final ContentType EVENT_STREAMS = create("text/event-stream", StandardCharsets.UTF_8); + + /** {@code *}{@code /*} wildcard content type that matches any MIME type. */ + public static final ContentType WILDCARD = create("*/*"); + + private final String mimeType; + private final Charset encoding; + private final boolean isBinary; + + /** + * Creates a new ContentType with the specified MIME type and no charset. + * + * @param mimeType the MIME type string (e.g., "application/json") + * @return a new ContentType instance + */ + public static ContentType create(String mimeType) { + return new ContentType(mimeType, null, false); + } + + /** + * Creates a new ContentType with the specified MIME type and charset. + * + * @param mimeType the MIME type string (e.g., "application/json") + * @param charset the character encoding for this content type + * @return a new ContentType instance + */ + public static ContentType create(String mimeType, Charset charset) { + return new ContentType(mimeType, charset, false); + } + + /** + * Creates a new ContentType with the specified MIME type and binary flag. + * + * @param mimeType the MIME type string (e.g., "image/png") + * @param isBinary {@code true} if this content type represents binary data + * @return a new ContentType instance + */ + public static ContentType create(String mimeType, boolean isBinary) { + return new ContentType(mimeType, null, isBinary); + } + + /** + * Constructs a new ContentType with the specified MIME type, encoding, and binary flag. + *

+ * If {@code isBinary} is {@code true}, the MIME type is registered in the internal + * set of known binary types for future lookups via {@link #isBinary(String)}. + *

+ * + * @param mimeType the MIME type string + * @param encoding the character encoding, or {@code null} if not specified + * @param isBinary {@code true} if this content type represents binary data + */ + ContentType(String mimeType, Charset encoding, boolean isBinary) { + this.mimeType = mimeType; + this.encoding = encoding; + this.isBinary = isBinary; + if(isBinary && !BINARY_TYPES.contains(mimeType)){ + BINARY_TYPES.add(mimeType); + } + } + + /** + * Determines if the given MIME type represents binary content. + *

+ * A MIME type is considered binary if: + *

+ *
    + *
  • It contains the word "binary"
  • + *
  • It has been registered as a binary type (via {@link #create(String, boolean)} with {@code isBinary=true})
  • + *
+ * + * @param mimeType the MIME type to check + * @return {@code true} if the MIME type represents binary content, {@code false} otherwise or if {@code mimeType} is {@code null} + */ + public static boolean isBinary(String mimeType) { + if(mimeType == null){ + return false; + } + String lc = mimeType.toLowerCase(); + return lc.contains("binary") || BINARY_TYPES.contains(lc); + } + + /** + * Returns the string representation of this content type. + *

+ * The format is {@code mimeType} if no charset is specified, or + * {@code mimeType; charset=encoding} if a charset is present. + * + * @return the content type string suitable for use in HTTP headers + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(mimeType); + if(encoding != null){ + sb.append("; charset=").append(encoding); + } + return sb.toString(); + } + + /** + * Returns the MIME type portion of this content type. + * + * @return the MIME type string (e.g., "application/json") + */ + public String getMimeType() { + return mimeType; + } + + /** + * Creates a new ContentType with the same MIME type but a different charset. + * + * @param charset the new character encoding + * @return a new ContentType instance with the specified charset + */ + public ContentType withCharset(Charset charset) { + return new ContentType(mimeType, charset, isBinary); + } + + /** + * Returns whether this content type represents binary data. + * + * @return {@code true} if this content type is binary + */ + public boolean isBinary() { + return isBinary; + } + + /** + * Returns the character encoding for this content type. + * + * @return the charset, or {@code null} if no charset is specified + */ + public Charset getCharset() { + return encoding; + } +} diff --git a/unirest/src/main/java/kong/unirest/core/Cookie.java b/unirest/src/main/java/kong/unirest/core/Cookie.java new file mode 100644 index 000000000..b4b553f31 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/Cookie.java @@ -0,0 +1,465 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.time.ZonedDateTime; +import java.util.*; +import java.util.stream.Collectors; + +/** + * Represents a cookie parsed from the Set-Cookie HTTP response header. + *

+ * This class provides functionality to parse, store, and serialize HTTP cookies + * as defined in RFC 6265. + *

+ *

+ * Cookies can be constructed either by providing name-value pairs directly, + * or by parsing a Set-Cookie header string. The class supports all standard + * cookie attributes including: + *

+ *
    + *
  • Domain - The domain to which the cookie applies
  • + *
  • Path - The URL path for which the cookie is valid
  • + *
  • Expires - The expiration date/time of the cookie
  • + *
  • Max-Age - The maximum age in seconds before the cookie expires
  • + *
  • Secure - Whether the cookie should only be sent over HTTPS
  • + *
  • HttpOnly - Whether the cookie is inaccessible to JavaScript
  • + *
  • SameSite - Cross-site request restrictions (None, Strict, Lax)
  • + *
  • Partitioned - Whether the cookie is partitioned (CHIPS)
  • + *
+ * + *

Usage Examples:

+ *
{@code
+ * // Create a simple cookie
+ * Cookie cookie = new Cookie("sessionId", "abc123");
+ *
+ * // Parse a Set-Cookie header
+ * Cookie parsed = new Cookie("sessionId=abc123; Path=/; HttpOnly; Secure");
+ *
+ * // Access cookie attributes
+ * String name = cookie.getName();
+ * String value = cookie.getValue();
+ * boolean isSecure = cookie.isSecure();
+ * }
+ * + *

+ * Note: The RFC 6265 specification is complex. For a more accessible explanation, + * see the Wikipedia article on HTTP cookies. + *

+ * + * @see RFC 6265 - HTTP State Management Mechanism + * @see Wikipedia - HTTP cookie + */ +public class Cookie { + private String name; + private String value; + private String domain; + private String path; + private boolean httpOnly; + private Integer maxAge; + private ZonedDateTime expires; + private boolean secure; + private SameSite sameSite; + private boolean partitioned; + + /** + * Constructs a cookie with the specified name and value. + *

+ * This creates a simple cookie without any additional attributes. + * Attributes can be set using the setter methods. + *

+ * + * @param name the cookie name (cookie-name) + * @param value the cookie value (cookie-value) + */ + public Cookie(String name, String value){ + this.name = name; + this.value = value; + } + + /** + * Constructs a cookie by parsing a Set-Cookie header value. + *

+ * The string is expected to be in the format defined by RFC 6265, + * with the name-value pair first, followed by semicolon-separated attributes. + * + *

+ * Example formats: + *

{@code
+     * "sessionId=abc123"
+     * "sessionId=abc123; Path=/; HttpOnly"
+     * "sessionId=abc123; Domain=.example.com; Secure; SameSite=Strict"
+     * }
+ * + * @param v the Set-Cookie header value to parse + */ + public Cookie(String v) { + this(v.split(";")); + } + + private Cookie(String[] split){ + int pos = 0; + for(String s : split){ + if(pos == 0){ + String[] sub = s.split("=",2); + name = sub[0]; + if (sub.length == 2) { + value = stripQuoteWrapper(sub[1]); + } else { + value = ""; + } + } else { + String[] sub = s.split("="); + parseSection(sub); + } + pos++; + } + } + + private String getDecode(String sub) { + try { + return URLDecoder.decode(sub, "UTF-8"); + } catch (UnsupportedEncodingException e) { + return sub; + } + } + + private String stripQuoteWrapper(String sub) { + if(sub.startsWith("\"") && sub.endsWith("\"") && sub.length() > 1){ + return sub.substring(1, sub.length() -1); + } + return sub; + } + + private void parseSection(String[] sub) { + switch (sub[0].toLowerCase().trim()) { + case "path": { + path = sub[1]; + break; + } + case "domain": { + domain = sub[1]; + break; + } + case "expires": { + parseExpires(sub[1]); + break; + } + case "max-age": { + maxAge = Integer.parseInt(sub[1]); + break; + } + case "httponly": { + httpOnly = true; + break; + } + case "partitioned": + partitioned = true; + break; + case "secure": { + secure = true; + break; + } + case "samesite": { + sameSite = SameSite.parse(sub[1]); + } + } + } + + private void parseExpires(String text) { + expires = Util.tryParseToDate(text); + } + + /** + * Returns the string representation of this cookie in Set-Cookie header format. + *

+ * The output includes the name-value pair followed by all set attributes, + * separated by semicolons, suitable for use in a Set-Cookie header. + *

+ * + * @return the cookie as a Set-Cookie header string + */ + @Override + public String toString() { + List pairs = new ArrayList<>(); + pairs.add(new Pair(name, value)); + if(path != null){ + pairs.add(new Pair("Path", path)); + } + if(domain != null){ + pairs.add(new Pair("Domain", domain)); + } + if(expires != null){ + pairs.add(new Pair("Expires", expires.format(Util.DEFAULT_PATTERN))); + } + if(maxAge != null){ + pairs.add(new Pair("Max-Age", String.valueOf(maxAge))); + } + if(httpOnly){ + pairs.add(new Pair("HttpOnly", null)); + } + if(secure){ + pairs.add(new Pair("Secure", null)); + } + if(partitioned){ + pairs.add(new Pair("Partitioned", null)); + } + return pairs.stream().map(Pair::toString).collect(Collectors.joining(";")); + } + + /** + * Sets the domain attribute of this cookie. + *

+ * The domain attribute specifies which hosts can receive the cookie. + * If not specified, the cookie is only sent to the origin server. + *

+ * + * @param domain the domain value (e.g., ".example.com") + */ + public void setDomain(String domain) { + this.domain = domain; + } + + /** + * Sets the path attribute of this cookie. + *

+ * The path attribute specifies the URL path that must exist in the + * requested URL for the browser to send the cookie. + *

+ * + * @param path the path value (e.g., "/api") + */ + public void setPath(String path) { + this.path = path; + } + + /** + * Sets the HttpOnly attribute of this cookie. + *

+ * When {@code true}, the cookie is inaccessible to JavaScript's + * {@code Document.cookie} API, helping to mitigate cross-site + * scripting (XSS) attacks. + *

+ * + * @param httpOnly {@code true} to make the cookie HTTP-only + */ + public void setHttpOnly(boolean httpOnly) { + this.httpOnly = httpOnly; + } + + /** + * Returns whether this cookie has the Partitioned attribute set. + *

+ * Partitioned cookies (also known as CHIPS - Cookies Having Independent + * Partitioned State) are stored separately for each top-level site, + * providing better privacy by preventing cross-site tracking. + *

+ * + * @return {@code true} if the cookie is partitioned + */ + public boolean isPartitioned() { + return this.partitioned; + } + + /** + * Sets the Partitioned attribute of this cookie. + *

+ * Partitioned cookies (CHIPS) are isolated by top-level site, + * which helps prevent cross-site tracking while still allowing + * embedded third-party content to maintain state. + *

+ * + * @param partitionedFlag {@code true} to make the cookie partitioned + * @see CHIPS Documentation + */ + public void setPartitioned(boolean partitionedFlag) { + this.partitioned = partitionedFlag; + } + + /** + * Sets the Secure attribute of this cookie. + *

+ * When {@code true}, the cookie is only sent to the server over + * HTTPS connections, never over unencrypted HTTP. + *

+ * + * @param secureFlag {@code true} to make the cookie secure-only + */ + public void setSecured(boolean secureFlag) { + this.secure = secureFlag; + } + + private static class Pair { + final String key; + final String value; + + public Pair(String key, String value){ + this.key = key; + this.value = value; + } + + @Override + public String toString() { + if(value == null){ + return key; + } + return key + "=" + value; + } + } + + /** + * @return the cookie-name + */ + public String getName() { + return name; + } + + /** + * @return the cookie-value + */ + public String getValue() { + return value; + } + + /** + * @return the cookie-value, url-decoded + */ + public String getUrlDecodedValue() { + return getDecode(value); + } + + /** + * @return the domain value of the cookie + */ + public String getDomain() { + return domain; + } + + /** + * @return the path value of the cookie + */ + public String getPath() { + return path; + } + + /** + * Per Wikipedia: + * The HttpOnly attribute directs browsers not to expose cookies through channels other than HTTP (and HTTPS) requests. + * This means that the cookie cannot be accessed via client-side scripting languages (notably JavaScript), + * and therefore cannot be stolen easily via cross-site scripting (a pervasive attack technique) + * @return a boolean if the cookie is httpOnly + */ + public boolean isHttpOnly() { + return httpOnly; + } + + /** + * Per Wikipedia: + * The Secure attribute is meant to keep cookie communication limited to encrypted transmission, + * directing browsers to use cookies only via secure/encrypted connections. + * @return a boolean of if the cookie is secure + */ + public boolean isSecure() { + return secure; + } + + /** + * Per Wikipedia: + * the Max-Age attribute can be used to set the cookie's expiration as an interval of seconds in the future, + * relative to the time the browser received the cookie. + * @return Max-Age attribute + */ + public int getMaxAge() { + return maxAge; + } + + /** + * Per Wikipedia: + * The Expires attribute defines a specific date and time for when the browser should delete the cookie. + * @return a ZonedDateTime of the expiration + */ + public ZonedDateTime getExpiration() { + return expires; + } + + /** + * returns the SameSite attribute + * @return the SameSite attribute if set. or null + */ + public SameSite getSameSite() { + return sameSite; + } + + /** + * The SameSite cookie attribute values. + *

+ * The SameSite attribute controls whether a cookie is sent with cross-site requests, + * providing protection gegen cross-site request forgery (CSRF) attacks. + *

+ * + * @see MDN SameSite Documentation + */ + public enum SameSite { + /** + * The cookie is sent with both cross-site and same-site requests. + * Requires the Secure attribute when used. + */ + None, + + /** + * The cookie is only sent with same-site requests (not with cross-site requests). + * Provides the strictest CSRF protection. + */ + Strict, + + /** + * The cookie is sent with same-site requests and top-level navigations + * from external sites (e.g., clicking a link), but not with cross-site + * subrequests (e.g., loading images or frames). + */ + Lax; + + private static EnumSet all = EnumSet.allOf(SameSite.class); + + /** + * Parses a string value to a SameSite enum constant. + *

+ * The comparison is case-insensitive. + *

+ * + * @param value the string value to parse (e.g., "Strict", "lax", "NONE") + * @return the matching SameSite constant, or {@code null} if no match is found + */ + public static SameSite parse(String value) { + return all.stream() + .filter(e -> e.name().equalsIgnoreCase(value)) + .findFirst() + .orElse(null); + } + } +} diff --git a/unirest/src/main/java/kong/unirest/core/CookieSpecs.java b/unirest/src/main/java/kong/unirest/core/CookieSpecs.java new file mode 100644 index 000000000..af604e7cf --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/CookieSpecs.java @@ -0,0 +1,60 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + + +/** + * Standard cookie specifications supported by Unirest. + */ +public final class CookieSpecs { + + /** + * The default policy. This policy provides a higher degree of compatibility + * with common cookie management of popular HTTP agents for non-standard + * (Netscape style) cookies. + */ + public static final String DEFAULT = "default"; + + /** + * The Netscape cookie draft compliant policy. + */ + public static final String NETSCAPE = "netscape"; + + /** + * The RFC 6265 compliant policy (interoprability profile). + */ + public static final String STANDARD = "standard"; + + /** + * The RFC 6265 compliant policy (strict profile) + */ + public static final String STANDARD_STRICT = "standard-strict"; + + /** + * The policy that ignores cookies. + */ + public static final String IGNORE_COOKIES = "ignoreCookies"; +} diff --git a/unirest/src/main/java/kong/unirest/core/Cookies.java b/unirest/src/main/java/kong/unirest/core/Cookies.java new file mode 100644 index 000000000..47e8ee11c --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/Cookies.java @@ -0,0 +1,57 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a collection of cookies with some helper methods for parsing and getting cookie + */ +public class Cookies extends ArrayList { + + public Cookies(){ + + } + + Cookies(List strings) { + strings.stream() + .map(Cookie::new) + .forEach(this::add); + } + + /** + * Get cookie by name + * @param name the name of the cookie you want + * @return the cookie with that name + */ + public Cookie getNamed(String name) { + return stream(). + filter(c -> c.getName().equalsIgnoreCase(name)) + .findFirst() + .orElse(null); + } +} diff --git a/unirest/src/main/java/kong/unirest/core/DefaultInterceptor.java b/unirest/src/main/java/kong/unirest/core/DefaultInterceptor.java new file mode 100644 index 000000000..196075f74 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/DefaultInterceptor.java @@ -0,0 +1,53 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.util.function.Consumer; + +class DefaultInterceptor implements Interceptor { + + private Consumer> consumer; + + @Override + public void onResponse(HttpResponse response, HttpRequestSummary request, Config config) { + if(consumer != null && !response.isSuccess()){ + consumer.accept(response); + } + } + + @Override + public HttpResponse onFail(Exception e, HttpRequestSummary request, Config config) { + throw new UnirestException(e); + } + + Consumer> getConsumer(){ + return consumer; + } + + void setConsumer(Consumer> consumer) { + this.consumer = consumer; + } +} diff --git a/unirest/src/main/java/kong/unirest/core/Empty.java b/unirest/src/main/java/kong/unirest/core/Empty.java new file mode 100644 index 000000000..aa769e82e --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/Empty.java @@ -0,0 +1,30 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +public class Empty { + private Empty(){} +} diff --git a/unirest/src/main/java/kong/unirest/core/FailedResponse.java b/unirest/src/main/java/kong/unirest/core/FailedResponse.java new file mode 100644 index 000000000..c0c30af91 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/FailedResponse.java @@ -0,0 +1,179 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * A failed response you COULD return if you want to live in a house of lies. + * This can be returned by a interceptor rather than throwing an exception. + * It's possible if not handled correctly this could be more confusing than the exception + */ +public class FailedResponse implements HttpResponse { + private final Exception failureReason; + + /** + * Build a elaborate lie from a failure. + * Just like what you're going to do at thanksgiving dinner. + * @param e where it all went wrong. + */ + public FailedResponse(Exception e) { + this.failureReason = e; + } + + /** + * Returns a 542, which is nothing and a lie. + * The remove server in this case returned nothing all all. + * As far as we know you aren't even on the internet. + * So we made up this code, because a 500+ status is better than 0 + * @return 542 + */ + @Override + public int getStatus() { + return 542; + } + + /** + * a error message of the exception + * @return a 'status' message + */ + @Override + public String getStatusText() { + return failureReason.getMessage(); + } + + /** + * @return a empty headers object because none was returned because there was no return + */ + @Override + public Headers getHeaders() { + return new Headers(); + } + + /** + * @return null, because there was no response + */ + @Override + public T getBody() { + return null; + } + + /** + * @return a parsing exception with the exception. + */ + @Override + public Optional getParsingError() { + return Optional.of(new UnirestParsingException(failureReason.getMessage(), failureReason)); + } + + /** + * @param func a function to transform a body type to something else. + * @param always null + * @return another object + */ + @Override + public V mapBody(Function func) { + return func.apply(null); + } + + /** + * @param func a function to transform a body type to something else. + * @param always null + * @return another response + */ + @Override + public HttpResponse map(Function func) { + return (HttpResponse) func.apply(null); + } + + /** + * @param consumer a function to consume a successful HttpResponse. + * This is never called in this case. + * @return this HttpResponse. + */ + @Override + public HttpResponse ifSuccess(Consumer> consumer) { + return this; + } + + /** + * @param consumer a function to consume a failed HttpResponse + * always called in this case + * @return this HttpResponse + */ + @Override + public HttpResponse ifFailure(Consumer> consumer) { + consumer.accept(this); + return this; + } + + /** + * @param errorClass the class to transform the body to. However as the body is null + * in this case it will also be null + * @param consumer a function to consume a failed HttpResponse + * always called in this case + * @return this HttpResponse + */ + @Override + public HttpResponse ifFailure(Class errorClass, Consumer> consumer) { + consumer.accept(null); + return this; + } + + /** + * is this a success? Obvs no! + * @return false + */ + @Override + public boolean isSuccess() { + return false; + } + + /** + * Map the body to an error object, however because the body in this case is always + * null this will always return null + * @param errorClass the class for the error + * @param the error type + * @return null + */ + @Override + public E mapError(Class errorClass) { + return null; + } + + @Override + public Cookies getCookies() { + return new Cookies(); + } + + @Override + public HttpRequestSummary getRequestSummary() { + return null; + } + +} diff --git a/unirest/src/main/java/kong/unirest/core/FilePart.java b/unirest/src/main/java/kong/unirest/core/FilePart.java new file mode 100644 index 000000000..cbdcbe0ee --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/FilePart.java @@ -0,0 +1,60 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.io.File; + +public class FilePart extends BodyPart { + private String fileName; + + public FilePart(File file, String name) { + this(file, name, null); + } + + public FilePart(File file, String name, String contentType) { + this(file, name, contentType, null); + } + + public FilePart(File file, String name, String contentType, Headers headers) { + super(file, name, contentType, headers); + this.fileName = file.getName(); + } + + @Override + public boolean isFile() { + return true; + } + + @Override + public String getFileName(){ + return this.fileName; + } + + @Override + public String toString() { + return String.format("%s=%s", getName(), fileName); + } +} diff --git a/unirest/src/main/java/kong/unirest/core/FileResponse.java b/unirest/src/main/java/kong/unirest/core/FileResponse.java new file mode 100644 index 000000000..e45cc1847 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/FileResponse.java @@ -0,0 +1,66 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.io.File; +import java.io.InputStream; +import java.nio.file.CopyOption; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class FileResponse extends BaseResponse { + private File body; + + public FileResponse(RawResponse r, String path, ProgressMonitor downloadMonitor, CopyOption... copyOptions) { + super(r); + try { + Path target = Paths.get(path); + InputStream content = getContent(r, downloadMonitor, target); + Files.copy(content, target, copyOptions); + body = target.toFile(); + } catch (Exception e) { + throw new UnrecoverableException(e); + } + } + + private InputStream getContent(RawResponse r, ProgressMonitor downloadMonitor, Path target) { + if(downloadMonitor == null){ + return r.getContent(); + } + return new MonitoringInputStream(r.getContent(), downloadMonitor, target, r); + } + + @Override + public File getBody() { + return body; + } + + @Override + protected String getRawBody() { + return null; + } +} diff --git a/unirest/src/main/java/kong/unirest/core/GenericType.java b/unirest/src/main/java/kong/unirest/core/GenericType.java new file mode 100644 index 000000000..2a73b39b2 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/GenericType.java @@ -0,0 +1,79 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +/** + * Parts of this file were taken from Jackson/core TypeReference under the Apache License: + * + * Apache (Software) License, version 2.0 ("the License"). + * See the License for details about distribution rights, and the + * specific rights regarding derivate works. + * + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * A class to hold onto generic type params for object mapping by creating a anonymous subtype. + * This is a common "trick" commonly used in Java to avoid issues with type erasure. + * + * Other examples can be found in popular libraries like Jackson, GSON, and Spring + * + *
+ *  GenericType ref = new GenericType<List<Integer>>() { };
+ * 
+ * @param the generic type you wish to represent. + */ +public abstract class GenericType implements Comparable> { + protected final Type type; + + protected GenericType() { + Type superClass = this.getClass().getGenericSuperclass(); + if (superClass instanceof Class) { + throw new IllegalArgumentException("Internal error: TypeReference constructed without actual type information"); + } else { + this.type = ((ParameterizedType)superClass).getActualTypeArguments()[0]; + } + } + + /** + * @return the Type which includes generic type information + */ + public Type getType() { + return this.type; + } + + public int compareTo(GenericType o) { + return 0; + } + + public Class getTypeClass(){ + return this.type.getClass(); + } +} diff --git a/unirest/src/main/java/kong/unirest/core/GetRequest.java b/unirest/src/main/java/kong/unirest/core/GetRequest.java new file mode 100644 index 000000000..7f3639822 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/GetRequest.java @@ -0,0 +1,29 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +public interface GetRequest extends HttpRequest { +} diff --git a/unirest/src/main/java/kong/unirest/core/Header.java b/unirest/src/main/java/kong/unirest/core/Header.java new file mode 100644 index 000000000..fe61c6260 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/Header.java @@ -0,0 +1,31 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +public interface Header { + String getName(); + String getValue(); +} diff --git a/unirest/src/main/java/kong/unirest/core/HeaderNames.java b/unirest/src/main/java/kong/unirest/core/HeaderNames.java new file mode 100644 index 000000000..ab21cf2a4 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/HeaderNames.java @@ -0,0 +1,203 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +/** + * Constants enumerating the HTTP headers. All headers defined in RFC1945 (HTTP/1.0), RFC2616 (HTTP/1.1), and RFC2518 + * (WebDAV) are listed. + * + * @since 4.1 + */ +public class HeaderNames { + + private HeaderNames(){} + + /** RFC 2616 (HTTP/1.1) Section 14.1 */ + public static final String ACCEPT = "Accept"; + + /** RFC 2616 (HTTP/1.1) Section 14.2 */ + public static final String ACCEPT_CHARSET = "Accept-Charset"; + + /** RFC 2616 (HTTP/1.1) Section 14.3 */ + public static final String ACCEPT_ENCODING = "Accept-Encoding"; + + /** RFC 2616 (HTTP/1.1) Section 14.4 */ + public static final String ACCEPT_LANGUAGE = "Accept-Language"; + + /** RFC 2616 (HTTP/1.1) Section 14.5 */ + public static final String ACCEPT_RANGES = "Accept-Ranges"; + + /** RFC 2616 (HTTP/1.1) Section 14.6 */ + public static final String AGE = "Age"; + + /** RFC 1945 (HTTP/1.0) Section 10.1, RFC 2616 (HTTP/1.1) Section 14.7 */ + public static final String ALLOW = "Allow"; + + /** RFC 1945 (HTTP/1.0) Section 10.2, RFC 2616 (HTTP/1.1) Section 14.8 */ + public static final String AUTHORIZATION = "Authorization"; + + /** RFC 2616 (HTTP/1.1) Section 14.9 */ + public static final String CACHE_CONTROL = "Cache-Control"; + + /** RFC 2616 (HTTP/1.1) Section 14.10 */ + public static final String CONNECTION = "Connection"; + + /** RFC 1945 (HTTP/1.0) Section 10.3, RFC 2616 (HTTP/1.1) Section 14.11 */ + public static final String CONTENT_ENCODING = "Content-Encoding"; + + /** RFC 2616 (HTTP/1.1) Section 14.12 */ + public static final String CONTENT_LANGUAGE = "Content-Language"; + + /** RFC 1945 (HTTP/1.0) Section 10.4, RFC 2616 (HTTP/1.1) Section 14.13 */ + public static final String CONTENT_LENGTH = "Content-Length"; + + /** RFC 2616 (HTTP/1.1) Section 14.14 */ + public static final String CONTENT_LOCATION = "Content-Location"; + + /** RFC 2616 (HTTP/1.1) Section 14.15 */ + public static final String CONTENT_MD5 = "Content-MD5"; + + /** RFC 2616 (HTTP/1.1) Section 14.16 */ + public static final String CONTENT_RANGE = "Content-Range"; + + /** RFC 1945 (HTTP/1.0) Section 10.5, RFC 2616 (HTTP/1.1) Section 14.17 */ + public static final String CONTENT_TYPE = "Content-Type"; + + /** RFC 1945 (HTTP/1.0) Section 10.6, RFC 2616 (HTTP/1.1) Section 14.18 */ + public static final String DATE = "Date"; + + /** RFC 2518 (WevDAV) Section 9.1 */ + public static final String DAV = "Dav"; + + /** RFC 2518 (WevDAV) Section 9.2 */ + public static final String DEPTH = "Depth"; + + /** RFC 2518 (WevDAV) Section 9.3 */ + public static final String DESTINATION = "Destination"; + + /** RFC 2616 (HTTP/1.1) Section 14.19 */ + public static final String ETAG = "ETag"; + + /** RFC 2616 (HTTP/1.1) Section 14.20 */ + public static final String EXPECT = "Expect"; + + /** RFC 1945 (HTTP/1.0) Section 10.7, RFC 2616 (HTTP/1.1) Section 14.21 */ + public static final String EXPIRES = "Expires"; + + /** RFC 1945 (HTTP/1.0) Section 10.8, RFC 2616 (HTTP/1.1) Section 14.22 */ + public static final String FROM = "From"; + + /** RFC 2616 (HTTP/1.1) Section 14.23 */ + public static final String HOST = "Host"; + + /** RFC 2518 (WevDAV) Section 9.4 */ + public static final String IF = "If"; + + /** RFC 2616 (HTTP/1.1) Section 14.24 */ + public static final String IF_MATCH = "If-Match"; + + /** RFC 1945 (HTTP/1.0) Section 10.9, RFC 2616 (HTTP/1.1) Section 14.25 */ + public static final String IF_MODIFIED_SINCE = "If-Modified-Since"; + + /** RFC 2616 (HTTP/1.1) Section 14.26 */ + public static final String IF_NONE_MATCH = "If-None-Match"; + + /** RFC 2616 (HTTP/1.1) Section 14.27 */ + public static final String IF_RANGE = "If-Range"; + + /** RFC 2616 (HTTP/1.1) Section 14.28 */ + public static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since"; + + /** RFC 1945 (HTTP/1.0) Section 10.10, RFC 2616 (HTTP/1.1) Section 14.29 */ + public static final String LAST_MODIFIED = "Last-Modified"; + + /** RFC 1945 (HTTP/1.0) Section 10.11, RFC 2616 (HTTP/1.1) Section 14.30 */ + public static final String LOCATION = "Location"; + + /** RFC 2518 (WevDAV) Section 9.5 */ + public static final String LOCK_TOKEN = "Lock-Token"; + + /** RFC 2616 (HTTP/1.1) Section 14.31 */ + public static final String MAX_FORWARDS = "Max-Forwards"; + + /** RFC 2518 (WevDAV) Section 9.6 */ + public static final String OVERWRITE = "Overwrite"; + + /** RFC 1945 (HTTP/1.0) Section 10.12, RFC 2616 (HTTP/1.1) Section 14.32 */ + public static final String PRAGMA = "Pragma"; + + /** RFC 2616 (HTTP/1.1) Section 14.33 */ + public static final String PROXY_AUTHENTICATE = "Proxy-Authenticate"; + + /** RFC 2616 (HTTP/1.1) Section 14.34 */ + public static final String PROXY_AUTHORIZATION = "Proxy-Authorization"; + + /** RFC 2616 (HTTP/1.1) Section 14.35 */ + public static final String RANGE = "Range"; + + /** RFC 1945 (HTTP/1.0) Section 10.13, RFC 2616 (HTTP/1.1) Section 14.36 */ + public static final String REFERER = "Referer"; + + /** RFC 2616 (HTTP/1.1) Section 14.37 */ + public static final String RETRY_AFTER = "Retry-After"; + + /** RFC 1945 (HTTP/1.0) Section 10.14, RFC 2616 (HTTP/1.1) Section 14.38 */ + public static final String SERVER = "Server"; + + /** RFC 2518 (WevDAV) Section 9.7 */ + public static final String STATUS_URI = "Status-URI"; + + /** RFC 2616 (HTTP/1.1) Section 14.39 */ + public static final String TE = "TE"; + + /** RFC 2518 (WevDAV) Section 9.8 */ + public static final String TIMEOUT = "Timeout"; + + /** RFC 2616 (HTTP/1.1) Section 14.40 */ + public static final String TRAILER = "Trailer"; + + /** RFC 2616 (HTTP/1.1) Section 14.41 */ + public static final String TRANSFER_ENCODING = "Transfer-Encoding"; + + /** RFC 2616 (HTTP/1.1) Section 14.42 */ + public static final String UPGRADE = "Upgrade"; + + /** RFC 1945 (HTTP/1.0) Section 10.15, RFC 2616 (HTTP/1.1) Section 14.43 */ + public static final String USER_AGENT = "User-Agent"; + + /** RFC 2616 (HTTP/1.1) Section 14.44 */ + public static final String VARY = "Vary"; + + /** RFC 2616 (HTTP/1.1) Section 14.45 */ + public static final String VIA = "Via"; + + /** RFC 2616 (HTTP/1.1) Section 14.46 */ + public static final String WARNING = "Warning"; + + /** RFC 1945 (HTTP/1.0) Section 10.16, RFC 2616 (HTTP/1.1) Section 14.47 */ + public static final String WWW_AUTHENTICATE = "WWW-Authenticate"; + +} diff --git a/unirest/src/main/java/kong/unirest/core/Headers.java b/unirest/src/main/java/kong/unirest/core/Headers.java new file mode 100644 index 000000000..e0e398d27 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/Headers.java @@ -0,0 +1,297 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.util.*; +import java.util.function.Supplier; + +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; + +/** + * Represents a collection of headers + */ +public class Headers { + + private static final long serialVersionUID = 71310341388734766L; + private List
headers = new ArrayList<>(); + + public Headers() { + } + + public Headers(Collection entries) { + entries.forEach(e -> add(e.name, e.value)); + } + + /** + * Add a header element + * @param name the name of the header + * @param value the value for the header + */ + public void add(String name, String value) { + add(name, () -> value); + } + + /** + * Add a header element with a supplier which will be evaluated on request + * @param name the name of the header + * @param value the value for the header + */ + public void add(String name, Supplier value) { + if (Objects.nonNull(name)) { + headers.add(new Entry(name, value)); + } + } + + /** + * Replace a header value. If there are multiple instances it will overwrite all of them + * @param name the name of the header + * @param value the value for the header + */ + public void replace(String name, String value) { + remove(name); + add(name, value); + } + + /** + * Remove a header by name + * @param name the name of the header + */ + public void remove(String name) { + headers.removeIf(h -> isName(h, name)); + } + + /** + * Get the number of header keys. + * @return the size of the header keys + */ + public int size() { + return headers.stream().map(Header::getName).collect(toSet()).size(); + } + + /** + * Get all the values for a header name + * @param name name of the header element + * @return a list of values + */ + public List get(String name) { + return headers.stream() + .filter(h -> isName(h, name)) + .map(Header::getValue) + .collect(toList()); + } + + /** + * Add a bunch of headers at once + * @param other a header + */ + public void putAll(Headers other) { + if(other != null) { + this.headers.addAll(other.headers); + } + } + + /** + * Check if a header is present + * @param name a header + * @return if the headers contain this name. + */ + public boolean containsKey(String name) { + return this.headers.stream().anyMatch(h -> isName(h, name)); + } + + /** + * Clear the headers! + */ + public void clear() { + this.headers.clear(); + } + + /** + * Get the first header value for a name + * @param key the name of the header + * @return the first value + */ + public String getFirst(String key) { + return headers + .stream() + .filter(h -> isName(h, key)) + .findFirst() + .map(Header::getValue) + .orElse(""); + } + + /** + * Get all of the headers + * @return all the headers, in order + */ + public List
all() { + return new ArrayList<>(this.headers); + } + + /** + * Add a Cookie header + * @param cookie the cookie + */ + public void cookie(Cookie cookie) { + headers.add(new Entry("cookie", cookie.toString())); + } + + /** + * sets a collection of cookies + * @param cookies some cookies + */ + public void cookie(Collection cookies) { + cookies.forEach(this::cookie); + } + + private boolean isName(Header h, String name) { + return Util.nullToEmpty(name).equalsIgnoreCase(h.getName()); + } + + void remove(String key, String value) { + List
header = headers.stream(). + filter(h -> key.equalsIgnoreCase(h.getName()) && value.equalsIgnoreCase(h.getValue())) + .collect(toList()); + headers.removeAll(header); + } + + /** + * @return list all headers like this:
Content-Length: 42
+     * Cache-Control: no-cache
+     * ...
+ */ + @Override + public String toString() { + final StringJoiner sb = new StringJoiner(System.lineSeparator()); + headers.forEach(header -> sb.add(header.toString())); + return sb.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { return true;} + if (o == null || getClass() != o.getClass()) { return false; } + Headers headers1 = (Headers) o; + return Objects.equals(headers, headers1.headers); + } + + @Override + public int hashCode() { + return Objects.hash(headers); + } + + /** + * creates a basic auth header from a username and password. + * The creds will be the Base64 encoded value of {username}:{password} along with the 'Basic' schema + * For example given a creds of "username" and "password" you would get + * eg: Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ= + * @param username the username + * @param password the password + */ + public void setBasicAuth(String username, String password) { + this.replace("Authorization", Util.toBasicAuthValue(username, password)); + } + + /** + * sets the Accept header + * @param value the value for accept + */ + public void accepts(String value) { + add(HeaderNames.ACCEPT, value); + } + + /** + * Sets headers based on a map of key-value pairs + * @param headerMap I'm the MAP + */ + public void add(Map headerMap) { + if (headerMap != null) { + for (Map.Entry entry : headerMap.entrySet()) { + add(entry.getKey(), entry.getValue()); + } + } + } + + /** + * Replace all headers from a given map. + * @param headerMap the map of headers + */ + public void replace(Map headerMap) { + if (headerMap != null) { + headerMap.forEach(this::replace); + } + } + + static class Entry implements Header { + + private final String name; + private final Supplier value; + + public Entry(String name, String value) { + this.name = name; + this.value = () -> value; + } + + public Entry(String name, Supplier value) { + this.name = name; + this.value = value == null ? () -> null : value; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getValue() { + String s = value.get(); + if(s == null){ + return ""; + } + return s; + } + + @Override + public boolean equals(Object o) { + if (this == o) { return true; } + if (o == null || getClass() != o.getClass()) { return false; } + Entry entry = (Entry) o; + return Objects.equals(name, entry.name) && + Objects.equals(value.get(), entry.value.get()); + } + + @Override + public int hashCode() { + return Objects.hash(name, value.get()); + } + + @Override + public String toString() { + return String.format("%s: %s",getName(), getValue()); + } + } +} diff --git a/unirest/src/main/java/kong/unirest/core/HttpMethod.java b/unirest/src/main/java/kong/unirest/core/HttpMethod.java new file mode 100644 index 000000000..95ef13c37 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/HttpMethod.java @@ -0,0 +1,101 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Represents an HTTP method (verb) such as GET, POST, or PUT. + *

+ * Unlike a fixed enum, instances are interned in an internal registry, so custom + * or non-standard verbs can be created and reused via {@link #valueOf(String)}. + * Standard verbs are provided as constants for convenience. + *

+ */ +public class HttpMethod { + private static final Map REGISTRY = new HashMap<>(); + + /** The HTTP GET method. */ + public static final HttpMethod GET = valueOf("GET"); + /** The HTTP POST method. */ + public static final HttpMethod POST = valueOf("POST"); + /** The HTTP QUERY method. */ + public static final HttpMethod QUERY = valueOf("QUERY"); + /** The HTTP PUT method. */ + public static final HttpMethod PUT = valueOf("PUT"); + /** The HTTP DELETE method. */ + public static final HttpMethod DELETE = valueOf("DELETE"); + /** The HTTP PATCH method. */ + public static final HttpMethod PATCH = valueOf("PATCH"); + /** The HTTP HEAD method. */ + public static final HttpMethod HEAD = valueOf("HEAD"); + /** The HTTP OPTIONS method. */ + public static final HttpMethod OPTIONS = valueOf("OPTIONS"); + /** The HTTP TRACE method. */ + public static final HttpMethod TRACE = valueOf("TRACE"); + /** The pseudo-method used for WebSocket connections. */ + public static final HttpMethod WEBSOCKET = valueOf("WEBSOCKET"); + + private final String name; + + private HttpMethod(String name){ + this.name = name; + } + + /** + * Return the interned {@link HttpMethod} for the given verb, creating it if necessary. + * The verb is normalized to upper case, so lookups are case-insensitive. + * @param verb the HTTP method name + * @return the corresponding HttpMethod instance + */ + public static HttpMethod valueOf(String verb){ + return REGISTRY.computeIfAbsent(String.valueOf(verb).toUpperCase(), HttpMethod::new); + } + + /** + * Return all HttpMethod instances currently registered. + * @return a set of all known HttpMethods + */ + public Set all(){ + return new HashSet<>(REGISTRY.values()); + } + + /** + * Return the name of this HTTP method. + * @return the method name in upper case + */ + public String name() { + return name; + } + + @Override + public String toString() { + return name; + } +} diff --git a/unirest/src/main/java/kong/unirest/core/HttpRequest.java b/unirest/src/main/java/kong/unirest/core/HttpRequest.java new file mode 100644 index 000000000..4f8914252 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/HttpRequest.java @@ -0,0 +1,451 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.io.File; +import java.net.http.HttpClient; +import java.nio.file.CopyOption; +import java.time.Instant; +import java.util.Collection; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + The primary request builder used to create a request. This will be completed after calling one of + the "as**" methods like asString() + */ +public interface HttpRequest { + /** + * add a route param that replaces the matching {name} + * For example routeParam("name", "fred") will replace {name} in + * https://localhost/users/{name} + * to + * https://localhost/users/fred + * + * @param name the name of the param (do not include curly braces {} + * @param value the value to replace the placeholder with + * @return this request builder + */ + R routeParam(String name, String value); + + /** + * add a route param map that replaces the matching {name} + * For example routeParam(Map.of("name", "fred")) will replace {name} in + * https://localhost/users/{name} + * to + * https://localhost/users/fred + * + * @param params a map of path params + * @return this request builder + */ + R routeParam(Map params); + + /** + * Basic auth credentials + * @param username the username + * @param password the password + * @return this request builder + */ + R basicAuth(String username, String password); + + /** + * The Accept header to send (e.g. application/json) + * @param value a valid mime type for the Accept header + * @return this request builder + */ + default R accept(ContentType value){ + Objects.requireNonNull(value); + return accept(value.toString()); + } + + /** + * The Accept header to send (e.g. application/json) + * @param value a valid mime type for the Accept header + * @return this request builder + */ + R accept(String value); + + /** + * The encoding to expect the response to be for cases where the server fails to respond with the proper encoding + * @param encoding a valid mime type for the Accept header + * @return this request builder + */ + R responseEncoding(String encoding); + + /** + * Add a http header, HTTP supports multiple of the same header. This will continue to append new values + * @param name name of the header + * @param value value for the header + * @return this request builder + */ + R header(String name, String value); + + /** + * Replace a header value or add it if it doesn't exist + * @param name name of the header + * @param value value for the header + * @return this request builder + */ + R headerReplace(String name, String value); + + /** + * Add headers as a map + * @param headerMap a map of headers + * @return this request builder + */ + R headers(Map headerMap); + + /** + * Replace headers as a map + * @param headerMap a map of headers + * @return this request builder + */ + R headersReplace(Map headerMap); + + /** + * Add a simple cookie header + * @param name the name of the cookie + * @param value the value of the cookie + * @return this request builder + */ + R cookie(String name, String value); + + /** + * Add a simple cookie header + * @param cookie a cookie + * @return this request builder + */ + R cookie(Cookie cookie); + + /** + * Add a collection of cookie headers + * @param cookies a cookie + * @return this request builder + */ + R cookie(Collection cookies); + + /** + * add a query param to the url. The value will be URL-Encoded + * @param name the name of the param + * @param value the value of the param + * @return this request builder + */ + R queryString(String name, Object value); + + /** + * Add multiple param with the same param name. + * queryString("name", Arrays.asList("bob", "linda")) will result in + * ?name=bob&name=linda + * @param name the name of the param + * @param value a collection of values + * @return this request builder + */ + R queryString(String name, Collection value); + + /** + * Add query params as a map of name value pairs + * @param parameters a map of params + * @return this request builder + */ + R queryString(Map parameters); + + /** + * Pass a ObjectMapper for the request. This will override any globally + * configured ObjectMapper + * @param mapper the ObjectMapper + * @return this request builder + */ + R withObjectMapper(ObjectMapper mapper); + + /** + * Set a timeout for this request + * @param millies the time in millies + * @return this request builder + */ + R requestTimeout(int millies); + + /** + * sets a download monitor for monitoring the response. this could be used for drawing a progress bar + * @param monitor a ProgressMonitor + * @return this request builder + */ + R downloadMonitor(ProgressMonitor monitor); + + + /** + * sets the HTTP version for the request. This will override any global config + * @param version the version + * @return this request builder + */ + R version(HttpClient.Version version); + + /** + * Executes the request and returns the response with the body mapped into a String + * @return response + */ + HttpResponse asString(); + + /** + * Executes the request asynchronously and returns the response with the body mapped into a String + * @return a CompletableFuture of a response + */ + CompletableFuture> asStringAsync(); + + /** + * Executes the request asynchronously and returns the response with the body mapped into a String + * @param callback a callback handler + * @return a CompletableFuture of a response + */ + CompletableFuture> asStringAsync(Callback callback); + + /** + * Executes the request and returns the response with the body mapped into a byte[] + * @return response + */ + HttpResponse asBytes(); + + + /** + * Executes the request asynchronously and returns the response with the body mapped into a byte[] + * @return a CompletableFuture of a response + */ + CompletableFuture> asBytesAsync(); + + /** + * Executes the request asynchronously and returns the response with the body mapped into a byte[] + * @param callback a callback handler + * @return a CompletableFuture of a response + */ + CompletableFuture> asBytesAsync(Callback callback); + + /** + * Executes the request and returns the response with the body mapped into a JsonNode + * @return response + */ + HttpResponse asJson(); + + /** + * Executes the request asynchronously and returns the response with the body mapped into a JsonNode + * @return a CompletableFuture of a response + */ + CompletableFuture> asJsonAsync(); + + /** + * Executes the request asynchronously and returns the response with the body mapped into a JsonNode + * @param callback a callback handler + * @return a CompletableFuture of a response + */ + CompletableFuture> asJsonAsync(Callback callback); + + /** + * Executes the request and returns the response with the body mapped into T by a configured ObjectMapper + * @param responseClass the class to return. This will be passed to the ObjectMapper + * @param the return type + * @return a response + */ + HttpResponse asObject(Class responseClass); + + /** + * Executes the request and returns the response with the body mapped into T by a configured ObjectMapper + * @param genericType the genertic type to return. This will be passed to the ObjectMapper + * @param the return type + * @return a response + */ + HttpResponse asObject(GenericType genericType); + + /** + * Execute the request and pass the raw response to a function for mapping. + * This raw response contains the original InputStream and is suitable for + * reading large responses. + * @param function the function to map the response into a object of T + * @param The type of the response mapping + * @return A HttpResponse containing T as the body + */ + HttpResponse asObject(Function function); + + /** + * Executes the request asynchronously and returns response with the body mapped into T by a configured ObjectMapper + * @param responseClass the class type to map to + * @param the return type + * @return a CompletableFuture of a response + */ + CompletableFuture> asObjectAsync(Class responseClass); + + /** + * Executes the request asynchronously, mapping to a type via the configured object mapper and then passed to a callback handler. + * @param responseClass the type for the ObjectMapper to map to + * @param callback a callback for handling the body post mapping + * @param the return type + * @return a CompletableFuture of a HttpResponse containing the body of T + */ + CompletableFuture> asObjectAsync(Class responseClass, Callback callback); + + /** + * Executes the request asynchronously, and use a GenericType with the ObjectMapper + * @param genericType the generic type containing the type + * @param the type of the response + * @return a CompletableFuture of a HttpResponse containing the body of T + */ + CompletableFuture> asObjectAsync(GenericType genericType); + + /** + * Executes the request asynchronously, and use a GenericType with the ObjectMapper + * @param genericType the generic type containing the type + * @param callback a callback for handling the body post mapping + * @param the type of the response + * @return a CompletableFuture of a HttpResponse containing the body of T + */ + CompletableFuture> asObjectAsync(GenericType genericType, Callback callback); + + /** + * Executes the request asynchronously, and pass the raw response to a function for mapping. + * This raw response contains the original InputStream and is suitable for + * reading large responses + * @param function a function to map the raw request into a object + * @param the type of the response + * @return a CompletableFuture of a HttpResponse containing the body of T + */ + CompletableFuture> asObjectAsync(Function function); + + /** + * Executes the request and writes the contents into a file + * @param path The path to the file. + * @param copyOptions options specifying how the copy should be done + * @return a HttpResponse with the file containing the results + */ + HttpResponse asFile(String path, CopyOption... copyOptions); + + /** + * asynchronously executes the request and writes the contents into a file + * @param path The path to the file. + * @param copyOptions options specifying how the copy should be done + * @return a file containing the results + */ + CompletableFuture> asFileAsync(String path, CopyOption... copyOptions); + + /** + * asynchronously executes the request and writes the contents into a file + * @param path The path to the file. + * @param callback a callback for handling the body post mapping + * @param copyOptions options specifying how the copy should be done + * @return a file containing the results + */ + CompletableFuture> asFileAsync(String path, Callback callback, CopyOption... copyOptions); + /** + * Allows for following paging links common in many APIs. + * Each request will result in the same request (headers, etc) but will use the "next" link provided by the extract function. + * + * @param the type of response. + * @param mappingFunction a function to return the desired return type leveraging one of the as* methods (asString, asObject, etc). + * @param linkExtractor a function to extract a "next" link to follow. Retuning a null or empty string ends the paging + * @return a PagedList of your type + */ + PagedList asPaged(Function mappingFunction, + Function, String> linkExtractor); + + /** + * Executes the request and returns the response without parsing the body + * @return the basic HttpResponse + */ + HttpResponse asEmpty(); + + /** + * Executes the request asynchronously and returns the response without parsing the body + * @return a CompletableFuture of a HttpResponse + */ + CompletableFuture> asEmptyAsync(); + + /** + * Executes the request asynchronously and returns a empty response which is passed to a callback + * @param callback the callback* Executes the request asynchronously and returns the response without parsing the body + * @return a CompletableFuture of a HttpResponse + */ + CompletableFuture> asEmptyAsync(Callback callback); + + + /** + * Execute the request and pass the raw response to a consumer. + * This raw response contains the original InputStream and is suitable for + * reading large responses + * @param consumer a consumer function + */ + void thenConsume(Consumer consumer); + + /** + * Execute the request asynchronously and pass the raw response to a consumer. + * This raw response contains the original InputStream and is suitable for + * reading large responses + * @param consumer a consumer function + */ + void thenConsumeAsync(Consumer consumer); + + /** + * @return The HTTP method of the request + */ + HttpMethod getHttpMethod(); + + /** + * @return The current URL string for the request + */ + String getUrl(); + + /** + * @return the current headers for the request + */ + Headers getHeaders(); + + /** + * @return if the request has a body it will be here. + */ + default Optional getBody(){ + return Optional.empty(); + } + + /** + * @return the connect timeout for this request + */ + Integer getRequestTimeout(); + + /** + * @return a summary for the response, used in metrics + */ + HttpRequestSummary toSummary(); + + /** + * @return the instant the request object was created in UTC (not when it was sent). + */ + Instant getCreationTime(); + + /** + * gets the version for the request, or null if not set. + * @return the version + */ + HttpClient.Version getVersion(); +} diff --git a/unirest/src/main/java/kong/unirest/core/HttpRequestBody.java b/unirest/src/main/java/kong/unirest/core/HttpRequestBody.java new file mode 100644 index 000000000..c9afcb298 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/HttpRequestBody.java @@ -0,0 +1,158 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import kong.unirest.core.json.JSONElement; + +import java.io.File; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.Map; +import java.util.Optional; + +class HttpRequestBody extends BaseRequest implements HttpRequestWithBody { + + private Charset charSet = StandardCharsets.UTF_8; + + public HttpRequestBody(Config config, HttpMethod method, String url) { + super(config, method, url); + } + + public HttpRequestBody(HttpRequestBody baseRequest) { + super(baseRequest); + this.charSet = baseRequest.getCharset(); + } + + @Override + public MultipartBody field(String name, Collection value) { + return new HttpRequestMultiPart(this).field(name, value); + } + + @Override + public MultipartBody field(String name, File file) { + return field(name, file, null); + } + + @Override + public MultipartBody field(MultiPartBuilder builder) { + return new HttpRequestMultiPart(this).forceMultiPart(true).field(builder); + } + + @Override + public MultipartBody field(String name, File file, String contentType) { + return new HttpRequestMultiPart(this).field(name, file, contentType); + } + + @Override + public MultipartBody field(String name, Object value) { + return field(name, value, null); + } + + @Override + public MultipartBody field(String name, Object value, String contentType) { + return new HttpRequestMultiPart(this).field(name, value, contentType); + } + + @Override + public MultipartBody fields(Map parameters) { + return new HttpRequestMultiPart(this).fields(parameters); + } + + @Override + public MultipartBody field(String name, InputStream stream, ContentType contentType, String fileName) { + return new HttpRequestMultiPart(this).field(name, stream, contentType, fileName); + } + + @Override + public MultipartBody multiPartContent() { + return new HttpRequestMultiPart(this).forceMultiPart(true); + } + + @Override + public MultipartBody field(String name, InputStream stream, String fileName) { + return field(name, stream, ContentType.APPLICATION_OCTET_STREAM, fileName); + } + + @Override + public HttpRequestBody charset(Charset charset) { + this.charSet = charset; + return this; + } + + @Override + public RequestBodyEntity body(JsonNode body) { + return body(body.toString()); + } + + @Override + public RequestBodyEntity body(String body) { + return new HttpRequestUniBody(this).body(body); + } + + @Override + public RequestBodyEntity body(InputStream body) { + return new HttpRequestUniBody(this).body(body); + } + + @Override + public RequestBodyEntity body(Object body) { + return new HttpRequestUniBody(this).body(body); + } + + @Override + public RequestBodyEntity body(byte[] body) { + return new HttpRequestUniBody(this).body(body); + } + + /** + * Sugar method for body operation + * + * @param body raw JSONElement + * @return RequestBodyEntity instance + */ + @Override + public RequestBodyEntity body(JSONElement body) { + return body(body.toString()); + } + + @Override + public Charset getCharset() { + return charSet; + } + + @Override + public HttpRequestWithBody contentType(String type) { + headers.add("Content-Type", type); + return this; + } + + @Override + public Optional getBody() { + return Optional.empty(); + } +} diff --git a/unirest/src/main/java/kong/unirest/core/HttpRequestJsonPatch.java b/unirest/src/main/java/kong/unirest/core/HttpRequestJsonPatch.java new file mode 100644 index 000000000..89f01cac5 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/HttpRequestJsonPatch.java @@ -0,0 +1,104 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.util.Optional; + +class HttpRequestJsonPatch extends BaseRequest implements JsonPatchRequest { + + private JsonPatch items = new JsonPatch(); + + HttpRequestJsonPatch(Config config, String url) { + super(config, HttpMethod.PATCH, url); + header("Content-Type", CONTENT_TYPE); + } + + public HttpRequestJsonPatch(HttpRequestJsonPatch baseRequest) { + super(baseRequest); + this.items = baseRequest.items; + } + + @Override + public JsonPatchRequest add(String path, Object value) { + items.add(path, value); + return this; + } + + @Override + public JsonPatchRequest remove(String path) { + items.remove(path); + return this; + } + + @Override + public JsonPatchRequest replace(String path, Object value) { + items.replace(path, value); + return this; + } + + @Override + public JsonPatchRequest test(String path, Object value) { + items.test(path, value); + return this; + } + + @Override + public JsonPatchRequest move(String from, String path) { + items.move(from, path); + return this; + } + + @Override + public JsonPatchRequest copy(String from, String path) { + items.copy(from, path); + return this; + } + + @Override + public Optional getBody() { + return Optional.of(this); + } + + @Override + public BodyPart uniPart() { + String bodyAsString = items.toString(); + return new UnibodyString(bodyAsString); + } + + @Override + public boolean isMultiPart() { + return false; + } + + @Override + public boolean isEntityBody() { + return true; + } + + public JsonPatch getPatch() { + return items; + } +} \ No newline at end of file diff --git a/unirest/src/main/java/kong/unirest/core/HttpRequestMultiPart.java b/unirest/src/main/java/kong/unirest/core/HttpRequestMultiPart.java new file mode 100644 index 000000000..34e655480 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/HttpRequestMultiPart.java @@ -0,0 +1,229 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.io.File; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.*; + +import static kong.unirest.core.MultiPartBuilder.named; + +class HttpRequestMultiPart extends BaseRequest implements MultipartBody { + private List parameters = new ArrayList<>(); + + private MultipartMode mode = MultipartMode.BROWSER_COMPATIBLE; + private Charset charSet; + private boolean forceMulti = false; + private ProgressMonitor monitor; + private String boundary; + + HttpRequestMultiPart(HttpRequestBody httpRequest) { + super(httpRequest); + this.charSet = httpRequest.getCharset(); + } + + HttpRequestMultiPart(HttpRequestMultiPart httpRequest) { + super(httpRequest); + this.charSet = httpRequest.getCharset(); + this.parameters = httpRequest.parameters; + this.forceMulti = httpRequest.forceMulti; + this.monitor = httpRequest.monitor; + this.boundary = httpRequest.boundary; + } + + @Override + public MultipartBody field(MultiPartBuilder builder) { + parameters.add(builder.toBodyPart()); + return this; + } + + @Override + public MultipartBody field(String name, String value) { + field(MultiPartBuilder.named(name).value(value)); + return this; + } + + @Override + public MultipartBody field(String name, String value, String contentType) { + field(named(name).value((Object) value).contentType(contentType)); + return this; + } + + @Override + public MultipartBody field(String name, String value, ContentType contentType) { + Objects.requireNonNull(contentType, "contentType is required"); + field(named(name).value((Object) value).contentType(contentType.getMimeType())); + return this; + } + + @Override + public MultipartBody field(String name, Collection collection) { + for (Object current: collection) { + field(named(name).value(current)); + } + return this; + } + + @Override + public MultipartBody field(String name, InputStream value, ContentType contentType) { + return field(named(name).value(value).contentType(contentType)); + } + + @Override + public MultipartBody field(String name, File file) { + return field(named(name).value(file)); + } + + @Override + public MultipartBody field(String name, File file, String contentType) { + return field(named(name).value(file).contentType(contentType)); + } + + @Override + public MultipartBody field(String name, InputStream stream, ContentType contentType, String fileName) { + return field(named(name).value(stream).contentType(contentType).fileName(fileName)); + } + + @Override + public MultipartBody field(String name, InputStream stream, String fileName) { + return field(named(name).value(stream).contentType(ContentType.APPLICATION_OCTET_STREAM).fileName(fileName)); + } + + @Override + public MultipartBody field(String name, byte[] bytes, ContentType contentType, String fileName) { + return field(named(name).value(bytes).contentType(contentType).fileName(fileName)); + } + + @Override + public MultipartBody field(String name, byte[] bytes, String fileName) { + return field(named(name).value(bytes).contentType(ContentType.APPLICATION_OCTET_STREAM).fileName(fileName)); + } + + @Override + public MultipartBody sortFields() { + Collections.sort(parameters); + return this; + } + + @Override + public MultipartBody charset(Charset charset) { + this.charSet = charset; + return this; + } + + @Override + public MultipartBody contentType(String mimeType) { + header(HeaderNames.CONTENT_TYPE, mimeType); + return this; + } + + @Override + public MultipartBody mode(MultipartMode value) { + this.mode = value; + return this; + } + + @Override + public MultipartBody uploadMonitor(ProgressMonitor uploadMonitor) { + this.monitor = uploadMonitor; + return this; + } + + @Override + public MultipartBody boundary(String boundaryIdentifier) { + this.boundary = boundaryIdentifier; + return this; + } + + @Override + public String getBoundary() { + if(boundary == null){ + boundary = UUID.randomUUID().toString(); + } + return boundary; + } + + @Override + public Charset getCharset() { + return this.charSet; + } + + public MultipartBody fields(Map fields) { + if (fields != null) { + for (Map.Entry param : fields.entrySet()) { + if (param.getValue() instanceof File) { + field(param.getKey(), (File) param.getValue()); + } else { + field(param.getKey(), Util.nullToEmpty(param.getValue())); + } + } + } + return this; + } + + public MultipartBody field(String name, Object value, String contentType) { + field(named(name).value(value).contentType(contentType)); + return this; + } + + @Override + public Optional getBody() { + return Optional.of(this); + } + + @Override + public boolean isMultiPart() { + return forceMulti || multiParts().stream().anyMatch(BodyPart::isFile); + } + + @Override + public boolean isEntityBody() { + return false; + } + + @Override + public Collection multiParts() { + return new ArrayList<>(parameters); + } + + @Override + public MultipartMode getMode() { + return mode; + } + + @Override + public ProgressMonitor getMonitor() { + return monitor; + } + + + MultipartBody forceMultiPart(boolean value) { + forceMulti = value; + headers.remove("Content-Type"); + return this; + } +} diff --git a/unirest/src/main/java/kong/unirest/core/HttpRequestNoBody.java b/unirest/src/main/java/kong/unirest/core/HttpRequestNoBody.java new file mode 100644 index 000000000..0f5f75cf2 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/HttpRequestNoBody.java @@ -0,0 +1,43 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.util.Optional; + +class HttpRequestNoBody extends BaseRequest implements GetRequest { + HttpRequestNoBody(Config config, HttpMethod method, String url) { + super(config, method, url); + } + + HttpRequestNoBody(HttpRequestNoBody baseRequest) { + super(baseRequest); + } + + @Override + public Optional getBody() { + return Optional.empty(); + } +} diff --git a/unirest/src/main/java/kong/unirest/core/HttpRequestSummary.java b/unirest/src/main/java/kong/unirest/core/HttpRequestSummary.java new file mode 100644 index 000000000..94e5f0892 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/HttpRequestSummary.java @@ -0,0 +1,59 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.util.Collection; + +/** + * A summary of a request about to be performed + */ +public interface HttpRequestSummary { + /** + * @return The HTTP method of the request + */ + HttpMethod getHttpMethod(); + + /** + * @return The current full URL string for the request with query params (http://somewhere/else?colour=red) + */ + String getUrl(); + + /** + * @return The raw un-parameterized path without query strings (http://somewhere/{param}) + */ + String getRawPath(); + + /** + * @return a string summary of the request suitable for logging + */ + String asString(); + + + /** + * @return an immutable collection of the headers for the request + */ + Collection
getHeaders(); +} diff --git a/unirest/src/main/java/kong/unirest/core/HttpRequestUniBody.java b/unirest/src/main/java/kong/unirest/core/HttpRequestUniBody.java new file mode 100644 index 000000000..eb2237a35 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/HttpRequestUniBody.java @@ -0,0 +1,139 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import kong.unirest.core.json.JSONElement; + +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.Optional; + +class HttpRequestUniBody extends BaseRequest implements RequestBodyEntity { + + private BodyPart body; + private Charset charSet; + private ProgressMonitor monitor; + + HttpRequestUniBody(HttpRequestBody httpRequest) { + super(httpRequest); + this.charSet = httpRequest.getCharset(); + } + + HttpRequestUniBody(HttpRequestUniBody httpRequest) { + super(httpRequest); + this.charSet = httpRequest.getCharset(); + this.body = httpRequest.body; + this.monitor = httpRequest.monitor; + } + + @Override + public RequestBodyEntity body(JsonNode jsonBody) { + return body(jsonBody.toString()); + } + + @Override + public RequestBodyEntity body(InputStream inputStreamBody) { + this.body = new InputStreamBody(inputStreamBody); + return this; + } + + @Override + public RequestBodyEntity body(JSONElement jsonBody) { + return body(jsonBody.toString()); + } + + @Override + public RequestBodyEntity body(Object objectBody) { + if(objectBody instanceof String){ + return body((String)objectBody); + } else if (objectBody instanceof JsonNode){ + return body((JsonNode) objectBody); + } else if (objectBody instanceof JSONElement){ + return body(objectBody.toString()); + } + return body(getObjectMapper().writeValue(objectBody)); + } + + @Override + public RequestBodyEntity body(String bodyAsString) { + this.body = new UnibodyString(bodyAsString); + return this; + } + + @Override + public RequestBodyEntity body(byte[] bodyBytes) { + this.body = new UniByteArrayBody(bodyBytes); + return this; + } + + @Override + public RequestBodyEntity charset(Charset charset) { + this.charSet = charset; + return this; + } + + @Override + public RequestBodyEntity contentType(String type) { + headers.add("Content-Type", type); + return this; + } + + @Override + public RequestBodyEntity uploadMonitor(ProgressMonitor progressMonitor) { + this.monitor = progressMonitor; + return this; + } + + @Override + public Optional getBody() { + return Optional.of(this); + } + + @Override + public Charset getCharset() { + return charSet; + } + + @Override + public boolean isMultiPart() { + return false; + } + + @Override + public boolean isEntityBody() { + return true; + } + + @Override + public BodyPart uniPart() { + return body; + } + + @Override + public ProgressMonitor getMonitor(){ + return monitor; + } +} diff --git a/unirest/src/main/java/kong/unirest/core/HttpRequestWithBody.java b/unirest/src/main/java/kong/unirest/core/HttpRequestWithBody.java new file mode 100644 index 000000000..eb082e44a --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/HttpRequestWithBody.java @@ -0,0 +1,209 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import kong.unirest.core.json.JSONElement; + +import java.io.File; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.Collection; +import java.util.Map; +import java.util.Objects; + +/** + * A request Builder for POST and PUT operations with a body. + * will switch to a MultipartBody once http form variables are introduced. + * or to a RequestBodyEntity + */ +public interface HttpRequestWithBody extends HttpRequest { + /** + * Forces the request to send as multipart even if all params are simple + * @return The same MultipartBody + */ + MultipartBody multiPartContent(); + + /** + * Sets a field param on the body. + * @param name the name of the field + * @param value a values + * @return this request builder + */ + MultipartBody field(String name, Object value); + + /** + * Sets multiple field params on the body each with the same name. + * @param name the name of the field + * @param value a Collection of values + * @return this request builder + */ + MultipartBody field(String name, Collection value); + + /** + * Sets a field param on the body with a specified content-type. + * @param name the name of the field + * @param value the object + * @param contentType contentType (i.e. application/xml) + * @return this request builder + */ + MultipartBody field(String name, Object value, String contentType); + + /** + * Sets multiple field params on the body from a map of key/value pairs. + * @param parameters the map of field params + * @return this request builder + */ + MultipartBody fields(Map parameters); + + /** + * Sets a File on the body. + * @param name the name of the file field + * @param file the file + * @return this request builder + */ + MultipartBody field(String name, File file); + + + /** + * Add a multipart field with a FieldBuilder which supports many different options + * @param builder a FieldBuilder + * @return this request builder + */ + MultipartBody field(MultiPartBuilder builder); + + /** + * Sets a File on the body with a specified content-type. + * @param name the name of the file field + * @param file the file + * @param contentType contentType (i.e. image/png) + * @return this request builder + */ + MultipartBody field(String name, File file, String contentType); + + /** + * Sets a File on the body from a raw InputStream requires a file name. + * @param name the name of the file field + * @param stream the inputStream + * @param fileName the name for the file + * @return this request builder + */ + MultipartBody field(String name, InputStream stream, String fileName); + + /** + * Sets a File on the body from a raw InputStream requires a specified content-type and file name. + * @param name the name of the file field + * @param stream the inputStream + * @param contentType contentType (i.e. image/png) + * @param fileName the name for the file + * @return this request builder + */ + MultipartBody field(String name, InputStream stream, ContentType contentType, String fileName); + + + /** + * Set the Charset encoding for the Content-Type. This is appended to the Content-Type Header + * (e.g. application/x-www-form-urlencoded; charset=US-ASCII) + * Default is UTF-8 + * @param charset the charset + * @return this request builder + */ + HttpRequestWithBody charset(Charset charset); + + /** + * Removes any Charset for the Content-Type for when servers cannot process it. + * (e.g. application/x-www-form-urlencoded) + * @return this request builder + */ + default HttpRequestWithBody noCharset() { + return charset(null); + } + + /** + * Set a String as the body of the request + * @param body the String + * @return this request builder + */ + RequestBodyEntity body(String body); + + /** + * Set a InputStream as the body + * @param body the Object + * @return this request builder + */ + RequestBodyEntity body(InputStream body); + + /** + * Set a Object as the body of the request. This will be serialized with one of the following methods: + * - Strings are native + * - JSONElements use their native toString + * - Everything else will pass through the supplied ObjectMapper + * @param body the Object + * @return this request builder + */ + RequestBodyEntity body(Object body); + + /** + * Set a byte array as the body of the request + * @param body the byte[] + * @return this request builder + */ + RequestBodyEntity body(byte[] body); + + /** + * Set JSON on the body + * @param body the JsonNode + * @return this request builder + */ + RequestBodyEntity body(JsonNode body); + + /** + * Set JSON on the body + * @param body the JSONElement + * @return this request builder + */ + RequestBodyEntity body(JSONElement body); + + /** + * get the current default charset + * @return the Charset + */ + Charset getCharset(); + + /** + * @param type The content mime type + * @return this request builder + */ + default HttpRequestWithBody contentType(ContentType type){ + Objects.requireNonNull(type); + return contentType(type.toString()).charset(type.getCharset()); + } + + /** + * @param type The content mime type + * @return this request builder + */ + HttpRequestWithBody contentType(String type); +} diff --git a/unirest/src/main/java/kong/unirest/core/HttpResponse.java b/unirest/src/main/java/kong/unirest/core/HttpResponse.java new file mode 100644 index 000000000..806ff29f7 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/HttpResponse.java @@ -0,0 +1,131 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * @param a Http Response holding a specific type of body. + */ +public interface HttpResponse { + + /** + * @return the HTTP status code. + */ + int getStatus(); + + /** + * @return status text + */ + String getStatusText(); + + /** + * @return Response Headers (map) with same case as server response. + * For instance use getHeaders().getFirst("Location") and not getHeaders().getFirst("location") to get first header "Location" + */ + Headers getHeaders(); + + /** + * @return the body + */ + T getBody(); + + /** + * If the transformation to the body failed by an exception it will be kept here + * @return a possible RuntimeException. Checked exceptions are wrapped in a UnirestException + */ + Optional getParsingError(); + + /** + * Map the body into another type + * @param func a function to transform a body type to something else. + * @param The return type of the function + * @return the return type + */ + V mapBody(Function func); + + /** + * Map the Response into another response with a different body + * @param func a function to transform a body type to something else. + * @param The return type of the function + * @return the return type + */ + HttpResponse map(Function func); + + /** + * If the response was a 200-series response. Invoke this consumer + * can be chained with ifFailure + * @param consumer a function to consume a HttpResponse + * @return the same response + */ + HttpResponse ifSuccess(Consumer> consumer); + + /** + * If the response was NOT a 200-series response or a mapping exception happened. Invoke this consumer + * can be chained with ifSuccess + * @param consumer a function to consume a HttpResponse + * @return the same response + */ + HttpResponse ifFailure(Consumer> consumer); + + + /** + * If the response was NOT a 200-series response or a mapping exception happened. map the original body into a error type and invoke this consumer + * can be chained with ifSuccess + * @param the type of error class to map the body + * @param errorClass the class of the error type to map to + * @param consumer a function to consume a HttpResponse + * @return the same response + */ + HttpResponse ifFailure(Class errorClass, Consumer> consumer); + + /** + * @return true if the response was a 200-series response and no mapping exception happened, else false + */ + boolean isSuccess(); + + /** + * Map the body into a error class if the response was NOT a 200-series response or a mapping exception happened. + * Uses the system Object Mapper + * @param the response type + * @param errorClass the class for the error + * @return the error object + */ + E mapError(Class errorClass); + + /** + * return a cookie collection parse from the set-cookie header + * @return a Cookies collection + */ + Cookies getCookies(); + + /** + * @return a Summary of the HttpRequest that created this response + */ + HttpRequestSummary getRequestSummary(); +} diff --git a/unirest/src/main/java/kong/unirest/core/HttpResponseSummary.java b/unirest/src/main/java/kong/unirest/core/HttpResponseSummary.java new file mode 100644 index 000000000..bf6fb66aa --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/HttpResponseSummary.java @@ -0,0 +1,41 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +/** + * A Summary of rhe response + */ +public interface HttpResponseSummary { + /** + * @return the http response code + */ + int getStatus(); + + /** + * @return the text of the http response code + */ + String getStatusText(); +} diff --git a/unirest/src/main/java/kong/unirest/core/HttpStatus.java b/unirest/src/main/java/kong/unirest/core/HttpStatus.java new file mode 100644 index 000000000..72f77cf65 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/HttpStatus.java @@ -0,0 +1,230 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +/** + * Constants for standard HTTP status codes as defined by IANA. + *

+ * This class provides named constants for HTTP response status codes, + * organized into the following categories: + *

+ *
    + *
  • 2xx Success - The request was successfully received, understood, and accepted
  • + *
  • 3xx Redirection - Further action needs to be taken to complete the request
  • + *
  • 4xx Client Error - The request contains bad syntax or cannot be fulfilled
  • + *
  • 5xx Server Error - The server failed to fulfill a valid request
  • + *
+ * + * @see IANA HTTP Status Code Registry + * @see RFC 7231 - HTTP/1.1 Semantics and Content + */ +public final class HttpStatus { + + // 2xx Success + + /** {@code 200 OK} - The request has succeeded. */ + public static final int OK = 200; + + /** {@code 201 Created} - The request has been fulfilled and a new resource has been created. */ + public static final int CREATED = 201; + + /** {@code 202 Accepted} - The request has been accepted for processing, but processing has not been completed. */ + public static final int ACCEPTED = 202; + + /** {@code 203 Non-Authoritative Information} - The returned metadata is not exactly the same as available from the origin server. */ + public static final int NON_AUTHORITATIVE_INFORMATION = 203; + + /** {@code 204 No Content} - The server has fulfilled the request but does not need to return a response body. */ + public static final int NO_CONTENT = 204; + + /** {@code 205 Reset Content} - The server has fulfilled the request and the user agent should reset the document view. */ + public static final int RESET_CONTENT = 205; + + /** {@code 206 Partial Content} - The server has fulfilled the partial GET request for the resource. */ + public static final int PARTIAL_CONTENT = 206; + + /** {@code 207 Multi-Status} - Provides status for multiple independent operations (WebDAV). */ + public static final int MULTI_STATUS = 207; + + /** {@code 208 Already Reported} - Used inside a DAV:propstat response element to avoid enumerating the internal members of multiple bindings (WebDAV). */ + public static final int ALREADY_REPORTED = 208; + + /** {@code 226 IM Used} - The server has fulfilled a GET request and the response is a representation of the result of one or more instance-manipulations. */ + public static final int IM_USED = 226; + + // 3xx Redirection + + /** {@code 300 Multiple Choices} - The target resource has more than one representation available. */ + public static final int MULTIPLE_CHOICE = 300; + + /** {@code 301 Moved Permanently} - The target resource has been assigned a new permanent URI. */ + public static final int MOVED_PERMANENTLY = 301; + + /** {@code 302 Found} - The target resource resides temporarily under a different URI. */ + public static final int FOUND = 302; + + /** {@code 303 See Other} - The server is redirecting the user agent to a different resource. */ + public static final int SEE_OTHER = 303; + + /** {@code 304 Not Modified} - The resource has not been modified since the version specified by the request headers. */ + public static final int NOT_MODIFIED = 304; + + /** {@code 305 Use Proxy} - The requested resource must be accessed through the proxy given by the Location field. Deprecated. */ + public static final int USE_PROXY = 305; + + /** {@code 306 (Unused)} - This status code is no longer used but is reserved. */ + public static final int UNUSED = 306; + + /** {@code 307 Temporary Redirect} - The target resource resides temporarily under a different URI; the user agent must not change the request method. */ + public static final int TEMPORARY_REDIRECT = 307; + + /** {@code 308 Permanent Redirect} - The target resource has been assigned a new permanent URI; the user agent must not change the request method. */ + public static final int PERMANENT_REDIRECT = 308; + + // 4xx Client Error + + /** {@code 400 Bad Request} - The server cannot process the request due to client error. */ + public static final int BAD_REQUEST = 400; + + /** {@code 401 Unauthorized} - The request requires user authentication. */ + public static final int UNAUTHORIZED = 401; + + /** {@code 402 Payment Required} - Reserved for future use. */ + public static final int PAYMENT_REQUIRED = 402; + + /** {@code 403 Forbidden} - The server understood the request but refuses to authorize it. */ + public static final int FORBIDDEN = 403; + + /** {@code 404 Not Found} - The server has not found anything matching the requested URI. */ + public static final int NOT_FOUND = 404; + + /** {@code 405 Method Not Allowed} - The method specified in the request is not allowed for the resource. */ + public static final int METHOD_NOT_ALLOWED = 405; + + /** {@code 406 Not Acceptable} - The resource is not available in a format acceptable to the request. */ + public static final int NOT_ACCEPTABLE = 406; + + /** {@code 407 Proxy Authentication Required} - The client must first authenticate itself with the proxy. */ + public static final int PROXY_AUTHENTICATION_REQUIRED = 407; + + /** {@code 408 Request Timeout} - The server timed out waiting for the request. */ + public static final int REQUEST_TIMEOUT = 408; + + /** {@code 409 Conflict} - The request could not be completed due to a conflict with the current state of the resource. */ + public static final int CONFLICT = 409; + + /** {@code 410 Gone} - The requested resource is no longer available and no forwarding address is known. */ + public static final int GONE = 410; + + /** {@code 411 Length Required} - The server requires a Content-Length header in the request. */ + public static final int LENGTH_REQUIRED = 411; + + /** {@code 412 Precondition Failed} - One or more preconditions given in the request header fields evaluated to false. */ + public static final int PRECONDITION_FAILED = 412; + + /** {@code 413 Payload Too Large} - The request entity is larger than the server is willing or able to process. */ + public static final int PAYLOAD_TOO_LARGE = 413; + + /** {@code 414 URI Too Long} - The request URI is longer than the server is willing to interpret. */ + public static final int URI_TOO_LONG = 414; + + /** {@code 415 Unsupported Media Type} - The server does not support the media type of the request entity. */ + public static final int UNSUPPORTED_MEDIA_TYPE = 415; + + /** {@code 416 Range Not Satisfiable} - The range specified in the Range header cannot be fulfilled. */ + public static final int RANGE_NOT_SATISFIABLE = 416; + + /** {@code 417 Expectation Failed} - The expectation given in the Expect header could not be met by the server. */ + public static final int EXPECTATION_FAILED = 417; + + /** {@code 418 I'm a teapot} - The server refuses to brew coffee because it is, permanently, a teapot (RFC 2324). */ + public static final int IM_A_TEAPOT = 418; + + /** {@code 421 Misdirected Request} - The request was directed at a server that is not able to produce a response. */ + public static final int MISDIRECTED_REQUEST = 421; + + /** {@code 422 Unprocessable Entity} - The server understands the content type but was unable to process the contained instructions (WebDAV). */ + public static final int UNPROCESSABLE_ENTITY = 422; + + /** {@code 423 Locked} - The resource that is being accessed is locked (WebDAV). */ + public static final int LOCKED = 423; + + /** {@code 424 Failed Dependency} - The request failed due to failure of a previous request (WebDAV). */ + public static final int FAILED_DEPENDENCY = 424; + + /** {@code 425 Too Early} - The server is unwilling to risk processing a request that might be replayed. */ + public static final int TOO_EARLY = 425; + + /** {@code 426 Upgrade Required} - The server refuses to perform the request using the current protocol but might after upgrade. */ + public static final int UPGRADE_REQUIRED = 426; + + /** {@code 428 Precondition Required} - The origin server requires the request to be conditional. */ + public static final int PRECONDITION_REQUIRED = 428; + + /** {@code 429 Too Many Requests} - The user has sent too many requests in a given amount of time (rate limiting). */ + public static final int TOO_MANY_REQUESTS = 429; + + /** {@code 431 Request Header Fields Too Large} - The server is unwilling to process the request because its header fields are too large. */ + public static final int REQUEST_HEADER_FIELDS_TOO_LARGE = 431; + + /** {@code 451 Unavailable For Legal Reasons} - The server is denying access to the resource as a consequence of a legal demand. */ + public static final int UNAVAILABLE_FOR_LEGAL_REASONS = 451; + + // 5xx Server Error + + /** {@code 500 Internal Server Error} - The server encountered an unexpected condition that prevented it from fulfilling the request. */ + public static final int INTERNAL_SERVER_ERROR = 500; + + /** {@code 501 Not Implemented} - The server does not support the functionality required to fulfill the request. */ + public static final int NOT_IMPLEMENTED = 501; + + /** {@code 502 Bad Gateway} - The server, acting as a gateway or proxy, received an invalid response from an upstream server. */ + public static final int BAD_GATEWAY = 502; + + /** {@code 503 Service Unavailable} - The server is currently unable to handle the request due to temporary overloading or maintenance. */ + public static final int SERVICE_UNAVAILABLE = 503; + + /** {@code 504 Gateway Timeout} - The server, acting as a gateway or proxy, did not receive a timely response from an upstream server. */ + public static final int GATEWAY_TIMEOUT = 504; + + /** {@code 505 HTTP Version Not Supported} - The server does not support the HTTP protocol version used in the request. */ + public static final int VERSION_NOT_SUPPORTED = 505; + + /** {@code 506 Variant Also Negotiates} - The server has an internal configuration error: transparent content negotiation resulted in a circular reference. */ + public static final int VARIANT_ALSO_NEGOTIATES = 506; + + /** {@code 507 Insufficient Storage} - The server is unable to store the representation needed to complete the request (WebDAV). */ + public static final int INSUFFICIENT_STORAGE = 507; + + /** {@code 508 Loop Detected} - The server detected an infinite loop while processing the request (WebDAV). */ + public static final int LOOP_DETECTED = 508; + + /** {@code 510 Not Extended} - Further extensions to the request are required for the server to fulfill it. */ + public static final int NOT_EXTENDED = 510; + + /** {@code 511 Network Authentication Required} - The client needs to authenticate to gain network access. */ + public static final int NETWORK_AUTHENTICATION_REQUIRED = 511; +} diff --git a/unirest/src/main/java/kong/unirest/core/InputStreamBody.java b/unirest/src/main/java/kong/unirest/core/InputStreamBody.java new file mode 100644 index 000000000..4ae142cbd --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/InputStreamBody.java @@ -0,0 +1,39 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.io.InputStream; + +class InputStreamBody extends BodyPart { + public InputStreamBody(InputStream body) { + super(body, null, null, null); + } + + @Override + public boolean isFile() { + return false; + } +} diff --git a/unirest/src/main/java/kong/unirest/core/InputStreamPart.java b/unirest/src/main/java/kong/unirest/core/InputStreamPart.java new file mode 100644 index 000000000..b9e1cf2ab --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/InputStreamPart.java @@ -0,0 +1,60 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.io.InputStream; + +public class InputStreamPart extends BodyPart { + private String fileName; + + InputStreamPart(String name, InputStream value, String contentType) { + this(name, value, contentType, null); + } + + InputStreamPart(String name, InputStream value, String contentType, String fileName) { + this(name, value, contentType, fileName, null); + } + + InputStreamPart(String name, InputStream value, String contentType, String fileName, Headers headers) { + super(value, name, contentType, headers); + this.fileName = fileName; + } + + @Override + public String getFileName() { + return fileName; + } + + @Override + public boolean isFile() { + return true; + } + + @Override + public String toString() { + return String.format("%s=%s", getName(), fileName); + } +} diff --git a/unirest/src/main/java/kong/unirest/core/Interceptor.java b/unirest/src/main/java/kong/unirest/core/Interceptor.java new file mode 100644 index 000000000..3ada8b11a --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/Interceptor.java @@ -0,0 +1,81 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +/** + * Each configuration of Unirest has an interceptor. + * This has three stages: + * * onRequest allows you to modify of view the request before it it sent + * * onResponse allows you to view the response. This includes both successful and failed but valid responses + * * onFail is called if a total connection or network failure occurred. + */ +public interface Interceptor { + /** + * Called just before a request. This can be used to view or modify the request. + * this could be used for things like + * - Logging the request + * - Injecting tracer headers + * - Record metrics + * The default implementation does nothing at all + * @param request the request + * @param config the current configuration + */ + default void onRequest(HttpRequest request, Config config) { + } + + /** + * Called just after the request. This can be used to view the response, + * Perhaps for logging purposes or just because you're curious. + * @param response the response + * @param request a summary of the request + * @param config the current configuration + */ + default void onResponse(HttpResponse response, HttpRequestSummary request, Config config) { + } + + /** + * Called in the case of a total failure. + * This would be where Unirest was completely unable to make a request at all for reasons like: + * - DNS errors + * - Connection failure + * - Connection or Socket timeout + * - SSL/TLS errors + * + * The default implimentation simply wraps the exception in a UnirestException and throws it. + * It is possible to return a different response object from the original if you really + * didn't want to every throw exceptions. Keep in mind that this is a lie + * + * Nevertheless, you could return something like a kong.unirest.core.FailedResponse + * + * @param e the exception + * @param request the original request + * @param config the current config + * @return a alternative response. + */ + default HttpResponse onFail(Exception e, HttpRequestSummary request, Config config) throws UnirestException { + throw new UnirestException(e); + } +} diff --git a/unirest/src/main/java/kong/unirest/core/JsonNode.java b/unirest/src/main/java/kong/unirest/core/JsonNode.java new file mode 100644 index 000000000..052321688 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/JsonNode.java @@ -0,0 +1,86 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import kong.unirest.core.json.JSONArray; +import kong.unirest.core.json.JSONException; +import kong.unirest.core.json.JSONObject; + +public class JsonNode { + + private JSONObject jsonObject; + private JSONArray jsonArray; + + private boolean array; + + public JsonNode(String json) { + if (json == null || "".equals(json.trim())) { + jsonObject = new JSONObject(); + } else { + try { + jsonObject = new JSONObject(json); + } catch (JSONException e) { + // It may be an array + jsonArray = new JSONArray(json); + array = true; + } + } + } + + public JSONObject getObject() { + return this.jsonObject; + } + + public JSONArray getArray() { + JSONArray result = this.jsonArray; + if (array == false) { + result = new JSONArray(); + result.put(jsonObject); + } + return result; + } + + public boolean isArray() { + return this.array; + } + + @Override + public String toString() { + if (isArray()) { + return jsonArray.toString(); + } else { + return jsonObject.toString(); + } + } + + public String toPrettyString() { + if (isArray()) { + return jsonArray.toString(2); + } else { + return jsonObject.toString(2); + } + } +} diff --git a/unirest/src/main/java/kong/unirest/core/JsonPatch.java b/unirest/src/main/java/kong/unirest/core/JsonPatch.java new file mode 100644 index 000000000..89a9b67bf --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/JsonPatch.java @@ -0,0 +1,84 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import kong.unirest.core.json.JSONArray; +import kong.unirest.core.json.JSONObject; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public class JsonPatch { + + private List items = new ArrayList<>(); + + public JsonPatch(){} + + public JsonPatch(String fromString){ + for (Object row : new JSONArray(fromString)) { + if(row instanceof JSONObject){ + items.add(new JsonPatchItem((JSONObject)row)); + } + } + } + + public void add(String path, Object value) { + items.add(new JsonPatchItem(JsonPatchOperation.add, path, value)); + } + + public void remove(String path) { + items.add(new JsonPatchItem(JsonPatchOperation.remove, path)); + } + + public void replace(String path, Object value) { + items.add(new JsonPatchItem(JsonPatchOperation.replace, path, value)); + } + + public void test(String path, Object value) { + items.add(new JsonPatchItem(JsonPatchOperation.test, path, value)); + } + + public void move(String from, String path) { + items.add(new JsonPatchItem(JsonPatchOperation.move, path, from)); + } + + public void copy(String from, String path) { + items.add(new JsonPatchItem(JsonPatchOperation.copy, path, from)); + } + + @Override + public String toString() { + JSONArray a = new JSONArray(); + items.forEach(i -> a.put(new JSONObject(i.toString()))); + return a.toString(); + } + + public Collection getOperations(){ + return Collections.unmodifiableList(items); + } +} \ No newline at end of file diff --git a/unirest/src/main/java/kong/unirest/core/JsonPatchItem.java b/unirest/src/main/java/kong/unirest/core/JsonPatchItem.java new file mode 100644 index 000000000..3c1d5332c --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/JsonPatchItem.java @@ -0,0 +1,96 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import kong.unirest.core.json.JSONObject; + +import java.util.Objects; + +public class JsonPatchItem { + private final JsonPatchOperation op; + private final String path; + private final Object value; + + public JsonPatchItem(JsonPatchOperation op, String path, Object value){ + this.op = op; + this.path = path; + this.value = value; + } + + public JsonPatchItem(JsonPatchOperation op, String path) { + this(op, path, null); + } + + public JsonPatchItem(JSONObject row) { + this.op = JsonPatchOperation.valueOf(row.getString("op")); + this.path = row.getString("path"); + if(row.has(op.getOperationtype())) { + this.value = row.get(op.getOperationtype()); + } else { + this.value = null; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) {return true;} + if (o == null || getClass() != o.getClass()) {return false;} + JsonPatchItem that = (JsonPatchItem) o; + return op == that.op && + Objects.equals(path, that.path) && + Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(op, path, value); + } + + @Override + public String toString() { + JSONObject json = new JSONObject() + .put("op", op) + .put("path", path); + + if(Objects.nonNull(value)){ + json.put(op.getOperationtype(), value); + } + + return json.toString(); + } + + public JsonPatchOperation getOp() { + return op; + } + + public String getPath() { + return path; + } + + public Object getValue() { + return value; + } +} \ No newline at end of file diff --git a/unirest/src/main/java/kong/unirest/core/JsonPatchOperation.java b/unirest/src/main/java/kong/unirest/core/JsonPatchOperation.java new file mode 100644 index 000000000..c7ab4818f --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/JsonPatchOperation.java @@ -0,0 +1,45 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +public enum JsonPatchOperation { + add("value"), + remove("value"), + replace("value"), + test("value"), + move("from"), + copy("from"); + + private final String operationtype; + + JsonPatchOperation(String operationtype) { + this.operationtype = operationtype; + } + + public String getOperationtype() { + return operationtype; + } +} diff --git a/unirest/src/main/java/kong/unirest/core/JsonPatchRequest.java b/unirest/src/main/java/kong/unirest/core/JsonPatchRequest.java new file mode 100644 index 000000000..ba9eb6f01 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/JsonPatchRequest.java @@ -0,0 +1,42 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +public interface JsonPatchRequest extends HttpRequest, Body { + String CONTENT_TYPE = "application/json-patch+json"; + + JsonPatchRequest add(String path, Object value); + + JsonPatchRequest remove(String path); + + JsonPatchRequest replace(String path, Object value); + + JsonPatchRequest test(String path, Object value); + + JsonPatchRequest move(String from, String path); + + JsonPatchRequest copy(String from, String path); +} diff --git a/unirest/src/main/java/kong/unirest/core/JsonResponse.java b/unirest/src/main/java/kong/unirest/core/JsonResponse.java new file mode 100644 index 000000000..d380f8468 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/JsonResponse.java @@ -0,0 +1,65 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.util.Objects; + +public class JsonResponse extends BaseResponse { + private JsonNode node; + + protected JsonResponse(RawResponse response) { + super(response); + node = getNode(response); + } + + private JsonNode getNode(RawResponse response) { + if (Objects.isNull(response) || !response.hasContent()) { + return new JsonNode(null); + } else { + String json = response.getContentAsString(); + return toJsonNode(json); + } + } + + private JsonNode toJsonNode(String json) { + try { + return new JsonNode(json); + } catch (RuntimeException e) { + super.setParsingException(json, e); + return null; + } + } + + @Override + public JsonNode getBody() { + return node; + } + + @Override + protected String getRawBody() { + return node.toString(); + } +} diff --git a/unirest/src/main/java/kong/unirest/core/MetricContext.java b/unirest/src/main/java/kong/unirest/core/MetricContext.java new file mode 100644 index 000000000..6d1dde0b4 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/MetricContext.java @@ -0,0 +1,42 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +/** + * A metric context for the current request + */ +@FunctionalInterface +public interface MetricContext { + /** + * Finishes a Http Request. Called just after the main request but before any mapping + * function including before any streaming responses (like file downloads) are complete. + * @param httpResponse a summary of the just executed response or null in the case of a system exception. + * @param ex an exception if one happened or null . + * Note that this is not just a 500 from the remote, + * but more likely a socket or connection timeout. + */ + void complete(HttpResponseSummary httpResponse, Exception ex); +} diff --git a/unirest/src/main/java/kong/unirest/core/MimeTypes.java b/unirest/src/main/java/kong/unirest/core/MimeTypes.java new file mode 100644 index 000000000..498ccf980 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/MimeTypes.java @@ -0,0 +1,92 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +/** + * A non-exclusive list of the most popular mime content types + */ +public class MimeTypes { + public static final String AU = "audio/basic"; + public static final String AVI = "video/msvideo,video/avi,video/x-msvideo"; + public static final String BMP = "image/bmp"; + public static final String BZ2 = "application/x-bzip2"; + public static final String CSS = "text/css"; + public static final String DOC = "application/msword"; + public static final String DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; + public static final String DOTX = "application/vnd.openxmlformats-officedocument.wordprocessingml.template"; + public static final String DTD = "application/xml-dtd"; + public static final String ES = "application/ecmascript"; + public static final String EXE = "application/octet-stream"; + public static final String EOT = "application/vnd.ms-fontobject"; + public static final String GIF = "image/gif"; + public static final String GZ = "application/x-gzip"; + public static final String HQX = "application/mac-binhex40"; + public static final String HTML = "text/html"; + public static final String ICO = "image/x-icon"; + public static final String JAR = "application/java-archive"; + public static final String JS = "application/javascript"; + public static final String JSON = "application/json"; + public static final String JPG = "image/jpeg"; + public static final String PNG = "image/png"; + public static final String PPTX = "application/vnd.openxmlformats-officedocument.presentationml.presentation"; + public static final String MPEG = "video/mpeg"; + public static final String OGG = "audio/vorbis,application/ogg"; + public static final String OTF = "application/font-otf"; + public static final String PDF = "application/pdf"; + public static final String POTX = "application/vnd.openxmlformats-officedocument.presentationml.template"; + public static final String PPSX = "application/vnd.openxmlformats-officedocument.presentationml.slideshow"; + public static final String PPT = "application/vnd.ms-powerpointtd"; + public static final String PS = "application/postscript"; + public static final String QT = "video/quicktime"; + public static final String MIDI = "audio/x-midi"; + public static final String MJS = "application/javascript"; + public static final String MP3 = "audio/mpeg"; + public static final String PL = "application/x-perl"; + public static final String RAM = "audio/x-pn-realaudio,audio/vnd.rn-realaudio"; + public static final String RAR = "application/x-rar-compressed"; + public static final String RDF = "application/rdf,application/rdf+xml"; + public static final String RTF = "application/rtf"; + public static final String SGML = "text/sgml"; + public static final String SIT = "application/x-stuffit"; + public static final String SLDX = "application/vnd.openxmlformats-officedocument.presentationml.slide"; + public static final String SVG = "image/svg+xml"; + public static final String SWF = "application/x-shockwave-flash"; + public static final String TGZ = "application/x-tar"; + public static final String TIFF = "image/tiff"; + public static final String TTF = "application/font-ttf"; + public static final String TSV = "text/tab-separated-values"; + public static final String TXT = "text/plain"; + public static final String WAV = "audio/wav,audio/x-wav"; + public static final String WOFF = "application/font-woff"; + public static final String WOF2 = "application/font-woff2"; + public static final String XLS = "application/vnd.ms-excel"; + public static final String XLAM = "application/vnd.ms-excel.addin.macroEnabled.12"; + public static final String XLSB = "application/vnd.ms-excel.sheet.binary.macroEnabled.12"; + public static final String XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; + public static final String XLTX = "application/vnd.openxmlformats-officedocument.spreadsheetml.template"; + public static final String XML = "application/xml"; + public static final String ZIP = "application/zip,application/x-compressed-zip"; +} diff --git a/unirest/src/main/java/kong/unirest/core/MonitoringInputStream.java b/unirest/src/main/java/kong/unirest/core/MonitoringInputStream.java new file mode 100644 index 000000000..9531be04a --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/MonitoringInputStream.java @@ -0,0 +1,133 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.Objects; +import java.util.zip.GZIPInputStream; + +public class MonitoringInputStream extends InputStream { + private final InputStream content; + private final ProgressMonitor downloadMonitor; + private final long totalSize; + private final String fileName; + private long byteCount = 0; + + public MonitoringInputStream(InputStream content, ProgressMonitor downloadMonitor, Path target, RawResponse rawResponse) { + this(content, downloadMonitor, target.getFileName().toString(), rawResponse); + } + + public MonitoringInputStream(InputStream content, ProgressMonitor downloadMonitor, String fileName, RawResponse rawResponse) { + Objects.requireNonNull(content, "Original InputStream is required"); + Objects.requireNonNull(downloadMonitor, "ProgressMonitor is required"); + Objects.requireNonNull(rawResponse, "RawResponse is required"); + + this.content = wrap(content, rawResponse); + this.downloadMonitor = downloadMonitor; + this.fileName = fileName; + this.totalSize = getBodySize(rawResponse); + } + + private InputStream wrap(InputStream is , RawResponse rawResponse) { + try { + if (is.available() > 0 && "gzip".equalsIgnoreCase(rawResponse.getContentType())) { + return new GZIPInputStream(is); + } else { + return is; + } + }catch (Exception e){ + throw new UnirestException(e); + } + } + + private Long getBodySize(RawResponse r) { + String header = r.getHeaders().getFirst("Content-Length"); + if (header != null && header.length() > 0) { + return Long.valueOf(header); + } + return 0L; + } + + @Override + public int read() throws IOException { + int read = content.read(); + if(read != -1){ + monitor(1); + } + return read; + } + + @Override + public int read(byte[] b) throws IOException { + int read = content.read(b); + monitor(read); + return read; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + int read = content.read(b, off, len); + monitor(read); + return read; + } + + private void monitor(int bytesRead) { + byteCount = byteCount + bytesRead; + downloadMonitor.accept("body", fileName, byteCount, totalSize); + } + + @Override + public void close() throws IOException { + content.close(); + } + + @Override + public long skip(long n) throws IOException { + return content.skip(n); + } + + @Override + public int available() throws IOException { + return content.available(); + } + + @Override + public synchronized void mark(int readlimit) { + content.mark(readlimit); + } + + @Override + public boolean markSupported() { + return content.markSupported(); + } + + @Override + public synchronized void reset() throws IOException { + content.reset(); + } +} diff --git a/unirest/src/main/java/kong/unirest/core/MultiPartBuilder.java b/unirest/src/main/java/kong/unirest/core/MultiPartBuilder.java new file mode 100644 index 000000000..83cd8d2ee --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/MultiPartBuilder.java @@ -0,0 +1,209 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.io.File; +import java.io.InputStream; + +/** + * A builder class for constructing multipart form data parts with customizable + * content type, headers, and file names. + *

+ * This builder supports various value types including strings, input streams, + * files, and byte arrays. Use the fluent API to configure the part and then + * call {@link #toBodyPart()} to create the final {@link BodyPart}. + *

+ * + *

Example usage:

+ *
{@code
+ * BodyPart part = MultiPartBuilder.named("file")
+ *     .value(new File("/path/to/file.txt"))
+ *     .contentType(ContentType.TEXT_PLAIN)
+ *     .fileName("custom-name.txt")
+ *     .header("X-Custom-Header", "value")
+ *     .toBodyPart();
+ * }
+ */ +public class MultiPartBuilder { + private final String partName; + private Object fieldValue; + private ContentType partContentType; + private Headers headers = new Headers(); + private String fName; + + /** + * Creates a new MultiPartBuilder with the specified part name. + * + * @param name the name of the multipart field + * @return a new MultiPartBuilder instance + */ + public static MultiPartBuilder named(String name) { + return new MultiPartBuilder(name); + } + + /** + * Constructs a new MultiPartBuilder with the specified part name. + * + * @param partName the name of the multipart field + */ + public MultiPartBuilder(String partName) { + this.partName = partName; + } + + /** + * Sets the value of this part to a string. + * + * @param value the string value for this part + * @return this builder for method chaining + */ + public MultiPartBuilder value(String value) { + this.fieldValue = value; + return this; + } + + /** + * Sets the value of this part to an input stream. + * + * @param inputStream the input stream containing the part data + * @return this builder for method chaining + */ + public MultiPartBuilder value(InputStream inputStream) { + this.fieldValue = inputStream; + return this; + } + + /** + * Sets the value of this part to a file. + * + * @param file the file to upload + * @return this builder for method chaining + */ + public MultiPartBuilder value(File file) { + this.fieldValue = file; + return this; + } + + /** + * Sets the value of this part to a byte array. + * + * @param content the byte array containing the part data + * @return this builder for method chaining + */ + public MultiPartBuilder value(byte[] content) { + this.fieldValue = content; + return this; + } + + /** + * Sets the value of this part to an object. + * First class types supported include File, InputStream and byte[] + * all other types will be converted to a string + * + * @param content the object being set for a value. + * @return this builder for method chaining + */ + public MultiPartBuilder value(Object content) { + this.fieldValue = content; + return this; + } + + /** + * Sets the file name for this part. + *

+ * This is useful when uploading input streams or byte arrays + * where the original file name is not available. + *

+ * + * @param fileName the file name to use for this part + * @return this builder for method chaining + */ + public MultiPartBuilder fileName(String fileName) { + this.fName = fileName; + return this; + } + + /** + * Sets the content type for this part using a string representation. + * + * @param contentType the MIME type string (e.g., "application/json") + * @return this builder for method chaining + */ + public MultiPartBuilder contentType(String contentType) { + return contentType(ContentType.create(contentType)); + } + + /** + * Sets the content type for this part. + * + * @param type the content type for this part + * @return this builder for method chaining + */ + public MultiPartBuilder contentType(ContentType type) { + this.partContentType = type; + return this; + } + + /** + * Adds a custom header to this part. + * + * @param name the header name + * @param value the header value + * @return this builder for method chaining + */ + public MultiPartBuilder header(String name, String value) { + this.headers.add(name, value); + return this; + } + + /** + * Builds and returns the appropriate {@link BodyPart} based on the configured value type. + *

+ * The returned type depends on the value set: + *

    + *
  • {@link InputStream} → {@link InputStreamPart}
  • + *
  • {@link File} → {@link FilePart}
  • + *
  • {@code byte[]} → {@link ByteArrayPart}
  • + *
  • Other (including String) → {@link ParamPart}
  • + *
+ * + * @return a BodyPart instance configured with the builder's settings + */ + public BodyPart toBodyPart() { + if (fieldValue instanceof InputStream) { + return new InputStreamPart(partName, (InputStream) fieldValue, getMimeType(), fName, headers); + } else if (fieldValue instanceof File) { + return new FilePart((File) fieldValue, partName, getMimeType(), headers); + } else if (fieldValue instanceof byte[]) { + return new ByteArrayPart(partName, (byte[]) fieldValue, partContentType, fName, headers); + } else { + return new ParamPart(partName, fieldValue == null ? "" : String.valueOf(fieldValue), getMimeType(), headers); + } + } + + private String getMimeType() { + return partContentType == null ? null : partContentType.getMimeType(); + } +} diff --git a/unirest/src/main/java/kong/unirest/core/MultipartBody.java b/unirest/src/main/java/kong/unirest/core/MultipartBody.java new file mode 100644 index 000000000..8ab72e4cf --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/MultipartBody.java @@ -0,0 +1,210 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.io.File; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.Collection; + +/** + * Represents a multi-part body builder for a request. + */ +public interface MultipartBody extends HttpRequest, Body { + /** + * add a simple field with a name and value + * @param name: the Name of the form field + * @param value: The string value for the field + * @return The same MultipartBody + */ + MultipartBody field(String name, String value); + + /** + * add a simple field with a name and value + * @param name: the Name of the form field + * @param values: A collection of values for the same name. + * @return The same MultipartBody + * + */ + MultipartBody field(String name, Collection values); + + /** + * add a simple field with a name and value + * @param name: the Name of the form field + * @param file: A File object. + * @return The same MultipartBody + */ + MultipartBody field(String name, File file); + + /** + * add a field using the MultiPartBuilder. + * This forces the request into a multipart request + * @param builder the builder + * @return the same MultipartBody + */ + MultipartBody field(MultiPartBuilder builder); + + /** + * add a simple field with a name and value + * @param name: the Name of the form field + * @param value: The string value for the field + * @param contentType: the content type of the value + * @return The same MultipartBody + * @deprecated will be removed in Unirest 5. + * advanced multipart fields will be supported though field(MultiPartBuilder builder) + */ + @Deprecated + MultipartBody field(String name, String value, String contentType); + + /** + * add a simple field with a name and value + * @param name: the Name of the form field + * @param value: The string value for the field + * @param contentType: the content type of the value + * @return The same MultipartBody + * @deprecated will be removed in Unirest 5. + * advanced multipart fields will be supported though field(MultiPartBuilder builder) + */ + @Deprecated + MultipartBody field(String name, String value, ContentType contentType); + + /** + * add a simple field with a name and value + * @param name: the Name of the form field + * @param file: A File object. + * @param contentType: the content mime-type of the file + * @return The same MultipartBody + * @deprecated will be removed in Unirest 5. + * advanced multipart fields will be supported though field(MultiPartBuilder builder) + */ + @Deprecated + MultipartBody field(String name, File file, String contentType); + + /** + * add a simple field with a name and value + * @param name: the Name of the form field + * @param value: A input stream + * @param contentType: the content mime-type of the file + * @return The same MultipartBody + * @deprecated will be removed in Unirest 5. + * advanced multipart fields will be supported though field(MultiPartBuilder builder) + */ + @Deprecated + MultipartBody field(String name, InputStream value, ContentType contentType); + + /** + * add a simple field with a name and value + * @param name: the Name of the form field + * @param stream: A input stream + * @param contentType: the content mime-type of the file + * @param fileName: the name of the file which will be included in the file part header + * @return The same MultipartBody + * @deprecated will be removed in Unirest 5. + * advanced multipart fields will be supported though field(MultiPartBuilder builder) + */ + @Deprecated + MultipartBody field(String name, InputStream stream, ContentType contentType, String fileName); + + /** + * add a simple field with a name and value + * @param name: the Name of the form field + * @param bytes: The raw bytes for the file + * @param contentType: the content mime-type of the file + * @param fileName: the name of the file which will be included in the file part header + * @return The same MultipartBody + * @deprecated will be removed in Unirest 5. + * advanced multipart fields will be supported though field(MultiPartBuilder builder) + */ + @Deprecated + MultipartBody field(String name, byte[] bytes, ContentType contentType, String fileName); + + /** + * add a simple field with a name and value + * @param name: the Name of the form field + * @param stream: The raw bytes for the file + * @param fileName: the name of the file which will be included in the file part header + * @return The same MultipartBody + * @deprecated will be removed in Unirest 5. + * advanced multipart fields will be supported though field(MultiPartBuilder builder) + */ + @Deprecated + MultipartBody field(String name, InputStream stream, String fileName); + + /** + * add a simple field with a name and value + * @param name: the Name of the form field + * @param bytes: The raw bytes for the file + * @param fileName: the name of the file which will be included in the file part header + * @return The same MultipartBody + * @deprecated will be removed in Unirest 5. + * advanced multipart fields will be supported though field(MultiPartBuilder builder) + */ + @Deprecated + MultipartBody field(String name, byte[] bytes, String fileName); + + /** + * sort fields on name, in alphabetical order + * @return The same MultipartBody + */ + MultipartBody sortFields(); + + /** + * Set the encoding of the request body + * @param charset the character set encoding of the body + * @return The same MultipartBody + */ + MultipartBody charset(Charset charset); + + /** + * Set the mime-type of the request body + * @param mimeType the mime type of the body + * @return The same MultipartBody + */ + MultipartBody contentType(String mimeType); + + /** + * Set the Apache Mode. + * @param value the value of the mode + * @return The same MultipartBody + * */ + MultipartBody mode(MultipartMode value); + + + /** + * Set a file Progress upload monitor suitable for drawing progress bars and whatnot. + * @param monitor a monitor + * @return The same MultipartBody + * */ + MultipartBody uploadMonitor(ProgressMonitor monitor); + + /** + * Sets the value to use as the boundary identifier. + * see rfc2046 + * @param boundaryIdentifier the value + * @return The same MultipartBody + */ + MultipartBody boundary(String boundaryIdentifier); +} diff --git a/unirest/src/main/java/kong/unirest/core/MultipartMode.java b/unirest/src/main/java/kong/unirest/core/MultipartMode.java new file mode 100644 index 000000000..57e1a888e --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/MultipartMode.java @@ -0,0 +1,35 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +public enum MultipartMode { + /** RFC 822, RFC 2045, RFC 2046 compliant */ + STRICT, + /** browser-compatible mode, i.e. only write Content-Disposition; use content charset */ + BROWSER_COMPATIBLE, + /** RFC 6532 compliant */ + RFC6532 +} diff --git a/unirest/src/main/java/kong/unirest/core/NoopMetric.java b/unirest/src/main/java/kong/unirest/core/NoopMetric.java new file mode 100644 index 000000000..7071c0d4d --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/NoopMetric.java @@ -0,0 +1,33 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +class NoopMetric implements UniMetric { + @Override + public MetricContext begin(HttpRequestSummary request) { + return (r,e) -> {}; + } +} diff --git a/unirest/src/main/java/kong/unirest/core/ObjectMapper.java b/unirest/src/main/java/kong/unirest/core/ObjectMapper.java new file mode 100644 index 000000000..b9428a997 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/ObjectMapper.java @@ -0,0 +1,63 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +/** + * Interface for object mappers that can transform response bodies to other structures. + */ +public interface ObjectMapper { + + /** + * reads the content from the request as a string and transforms to a type passed by the + * asObject method on the Unirest builder. + * @param value the content as a string. + * @param valueType the type to map to + * @return the object mapped into the class type + * @param the type + */ + T readValue(String value, Class valueType); + + /** + * reads the content from the request as a string and transforms to a type passed by the + * asObject method on the Unirest builder. + * This method takes a GenericType which retains Generics information for types lke List<Foo> + * @param value the content as a string. + * @param genericType the generic type + * @return the object mapped into the class type + * @param the type + */ + default T readValue(String value, GenericType genericType){ + throw new UnirestException("Please implement me"); + } + + /** + * Takes a object and serialize it as a string. + * This is used to map objects to bodies to pass to requests + * @param value the object to serialize to a string + * @return the serialized string of the object + */ + String writeValue(Object value); +} diff --git a/unirest/src/main/java/kong/unirest/core/ObjectResponse.java b/unirest/src/main/java/kong/unirest/core/ObjectResponse.java new file mode 100644 index 000000000..1e6b39d11 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/ObjectResponse.java @@ -0,0 +1,82 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.util.Optional; +import java.util.function.Function; + + +class ObjectResponse extends BaseResponse { + private final T body; + private final ObjectMapper om; + private String rawBody; + + ObjectResponse(ObjectMapper om, RawResponse response, Class to) { + super(response); + this.om = om; + this.body = readBody(response) + .map(s -> getBody(s, e -> om.readValue(e, to))) + .orElse(null); + } + + ObjectResponse(ObjectMapper om, RawResponse response, GenericType to){ + super(response); + this.om = om; + this.body = readBody(response) + .map(s -> getBody(s, e -> om.readValue(e, to))) + .orElse(null); + } + + private Optional readBody(RawResponse response) { + if(!response.hasContent()){ + return Optional.empty(); + } + String s = response.getContentAsString(); + if(response.getStatus() >= 400){ + rawBody = s; + } + return Optional.of(s); + } + + private T getBody(String b, Function func){ + try { + return func.apply(b); + } catch (RuntimeException e) { + setParsingException(b, e); + return null; + } + } + + @Override + public T getBody() { + return body; + } + + @Override + protected String getRawBody() { + return rawBody; + } +} diff --git a/unirest/src/main/java/kong/unirest/core/PagedList.java b/unirest/src/main/java/kong/unirest/core/PagedList.java new file mode 100644 index 000000000..82762b66a --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/PagedList.java @@ -0,0 +1,66 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +public class PagedList extends ArrayList> { + + /** + * @return Returns all successful bodies + */ + public List getBodies() { + return stream() + .filter(HttpResponse::isSuccess) + .map(HttpResponse::getBody) + .collect(Collectors.toList()); + } + + /** + * For each successful response If the response was a 200-series response. Invoke this consumer + * can be chained with ifFailure + * @param consumer a function to consume a HttpResponse + * @return the same paged list + */ + public PagedList ifSuccess(Consumer> consumer) { + stream().filter(HttpResponse::isSuccess).forEach(consumer); + return this; + } + + /** + * For each failed response if the response was NOT a 200-series response or a mapping exception happened. Invoke this consumer + * can be chained with ifSuccess + * @param consumer a function to consume a HttpResponse + * @return the same paged list + */ + public PagedList ifFailure(Consumer> consumer) { + stream().filter(r -> !r.isSuccess()).forEach(consumer); + return this; + } +} diff --git a/unirest/src/main/java/kong/unirest/core/ParamPart.java b/unirest/src/main/java/kong/unirest/core/ParamPart.java new file mode 100644 index 000000000..296c7ab50 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/ParamPart.java @@ -0,0 +1,57 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; + +public class ParamPart extends BodyPart { + ParamPart(String name, String value) { + this(name, value, null); + } + + ParamPart(String name, String value, String contentType) { + this(name, value, contentType, null); + } + + ParamPart(String name, String value, String contentType, Headers headers) { + super(value, name, contentType, headers); + } + + @Override + public boolean isFile() { + return false; + } + + @Override + public String toString() { + try { + return String.format("%s=%s", getName(), URLEncoder.encode(getValue(), "UTF-8")); + } catch (UnsupportedEncodingException e) { + throw new UnirestException(e); + } + } +} diff --git a/unirest/src/main/java/kong/unirest/core/Path.java b/unirest/src/main/java/kong/unirest/core/Path.java new file mode 100644 index 000000000..40f6263f4 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/Path.java @@ -0,0 +1,187 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.Collection; +import java.util.Map; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Class for building a URI with query params + */ +public class Path { + private String url; + private String rawPath; + + /** + * construct a path + * @param url the URL + */ + public Path(String url) { + this(url, null); + } + + /** + * Construct a path with a URL that could be relative and a default base for it + * @param url the url + * @param defaultBasePath the default base + */ + Path(String url, String defaultBasePath) { + if(defaultBasePath != null && url != null && !url.toLowerCase().startsWith("http")){ + String full = defaultBasePath + url; + this.url = full; + this.rawPath = full; + } else { + this.url = url; + this.rawPath = url; + } + } + + /** + * replace path params designated with curley braces with a value + * @param params a map of param names and values + */ + public void param(Map params) { + params.forEach((key, value) -> param(key, String.valueOf(value))); + } + + /** + * replace a single path param by name + * @param name the name of the path param + * @param value the value to replace it with + */ + public void param(String name, String value) { + Matcher matcher = Pattern.compile("\\{" + name + "\\}").matcher(url); + if (!matcher.find()) { + throw new UnirestException("Can't find route parameter name \"" + name + "\""); + } + this.url = matcher.replaceAll(encodePath(value)); + } + + private String encodePath(String value) { + if(value == null){ + return ""; + } + return Util.encode(value).replaceAll("\\+", "%20"); + } + + /** + * Add a query param. This will result in a query param per value + * @param name the name + * @param value a collection of values + */ + public void queryString(String name, Collection value){ + for (Object cur : value) { + queryString(name, cur); + } + } + + /** + * Add a query param + * @param name the name + * @param value the value + */ + public void queryString(String name, Object value) { + StringBuilder queryString = new StringBuilder(); + if (url.contains("?")) { + queryString.append("&"); + } else { + queryString.append("?"); + } + try { + queryString.append(URLEncoder.encode(name, "UTF-8")); + if(value != null) { + queryString.append("=").append(URLEncoder.encode(String.valueOf(value), "UTF-8")); + } + } catch (UnsupportedEncodingException e) { + throw new UnirestException(e); + } + url += queryString.toString(); + } + + /** + * Add query params as a map of key/values + * @param parameters the params to add + */ + public void queryString(Map parameters) { + if (parameters != null) { + for (Map.Entry param : parameters.entrySet()) { + queryString(param.getKey(), param.getValue()); + } + } + } + + @Override + public String toString() { + return escape(url); + } + + private String escape(String string) { + return string.replaceAll(" ", "%20").replaceAll("\t", "%09"); + } + + /** + * @return the full raw path + */ + public String rawPath() { + return rawPath; + } + + /** + * @return the URL without the query string + */ + public String baseUrl() { + if(url != null && url.contains("?")){ + return url.substring(0, url.indexOf("?")); + } + return url; + } + + /** + * @return just the query string + */ + public String getQueryString(){ + return url.substring(url.indexOf("?")+1); + } + + @Override + public boolean equals(Object o) { + if (this == o) { return true; } + if (o == null || getClass() != o.getClass()) { return false; } + Path path = (Path) o; + return Objects.equals(url, path.url); + } + + @Override + public int hashCode() { + return Objects.hash(url); + } +} + diff --git a/unirest/src/main/java/kong/unirest/core/ProgressMonitor.java b/unirest/src/main/java/kong/unirest/core/ProgressMonitor.java new file mode 100644 index 000000000..2f9142d8d --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/ProgressMonitor.java @@ -0,0 +1,50 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +/** + * A ProgressMonitor is a functional interface which can be passed to unirest for the purposes of + * monitoring uploads and downloads. A common use case is for drawing progress bars. + * + * If an upload contains multiple files each one is called individually and the file name is provided. + * + * note that you will not receive a total for ALL files together at once. + * If you wanted this you can keep track of the total bytes of files you planned to upload and then + * have your ProgressMonitor aggregate the results. + */ +@FunctionalInterface +public interface ProgressMonitor { + /** + * Accept stats about the current file upload chunk for a file. + * @param field the field name, or 'body' on non-multipart uploads/downloads + * @param fileName the name of the file in question if available (InputStreams and byte arrays may not have file names) + * @param bytesWritten the number of bytes that have been uploaded or downloaded so far + * @param totalBytes the total bytes that will be uploaded or downloaded. + * On downloads this depends on the Content-Length header be returned + * On uploads this this may be an estimate if an InputStream was used + * */ + void accept(String field, String fileName, Long bytesWritten, Long totalBytes); +} diff --git a/unirest/src/main/java/kong/unirest/core/Proxy.java b/unirest/src/main/java/kong/unirest/core/Proxy.java new file mode 100644 index 000000000..d20064ef9 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/Proxy.java @@ -0,0 +1,97 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +/** + * represents basic proxy settings + * When this is used to configure the client it sets the following: + * Proxy host and port are configured as a simple java.net.ProxySelector and set as the default selector + * Username and password are configured as a single java.net.Authenticator which will apply to ALL auth challenges (not just the proxy) + */ +public class Proxy { + private final String host; + private final Integer port; + private final String username; + private final String password; + + /** + * Construct just based on host and port + * @param host hostname + * @param port the port + */ + public Proxy(String host, Integer port){ + this(host, port, null, null); + } + + /** + * Construct with all the things + * @param host hostname + * @param port the port + * @param username username for authentication + * @param password password for authentication + */ + public Proxy(String host, Integer port, String username, String password) { + this.host = host; + this.port = port; + this.username = username; + this.password = password; + } + + /** + * @return the host + */ + public String getHost() { + return host; + } + + /** + * @return the port + */ + public Integer getPort() { + return port; + } + + /** + * @return the username for authentication + */ + public String getUsername() { + return username; + } + + /** + * @return the password + */ + public String getPassword() { + return password; + } + + /** + * @return indicates that the username and password have been configured. + */ + public boolean isAuthenticated() { + return username != null && password != null; + } +} diff --git a/unirest/src/main/java/kong/unirest/core/QueryParams.java b/unirest/src/main/java/kong/unirest/core/QueryParams.java new file mode 100644 index 000000000..9c29b2fab --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/QueryParams.java @@ -0,0 +1,96 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.Collection; + +public class QueryParams { + private final Collection pairs; + + public static QueryParams fromURI(String uri) { + return new QueryParams(uri); + } + + public static QueryParams fromBody(String body) { + QueryParams queryParams = new QueryParams(); + queryParams.parse(body); + return queryParams; + } + + private QueryParams() { + pairs = new ArrayList<>(); + } + + private QueryParams(String url) { + this(); + String[] urlParts = url.split("\\?"); + if (urlParts.length > 1) { + parse(urlParts[1]); + } + } + + private void parse(String urlPart) { + try { + String query = urlPart; + for (String param : query.split("&")) { + String[] pair = param.split("="); + String key = URLDecoder.decode(pair[0], "UTF-8"); + String value = ""; + if (pair.length > 1) { + value = URLDecoder.decode(pair[1], "UTF-8"); + } + pairs.add(new NameValuePair(key, value)); + } + } catch (Exception e) { + throw new UnirestException(e); + } + } + + + public Collection getQueryParams() { + return new ArrayList<>(pairs); + } + + public static class NameValuePair { + private final String key; + private final String value; + + public NameValuePair(String name, String value) { + this.key = name; + this.value = value; + } + + public String getName() { + return key; + } + + public String getValue() { + return value; + } + } +} \ No newline at end of file diff --git a/unirest/src/main/java/kong/unirest/core/RawResponse.java b/unirest/src/main/java/kong/unirest/core/RawResponse.java new file mode 100644 index 000000000..ddd93b2e7 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/RawResponse.java @@ -0,0 +1,148 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.io.InputStream; +import java.io.InputStreamReader; + +/** + * The Raw Response represents the response before any mapping or consumption of the content. + */ +public interface RawResponse { + /** + * Returns the status code for this response. + * + * @return the response code + */ + int getStatus(); + + /** + * Returns the status text for this response. + * + * @return the response text + */ + String getStatusText(); + + /** + * Returns the received response headers. + * + * @return the response headers + */ + Headers getHeaders(); + + /** + * Returns the body content of the response as a InputStream. + * Like most InputStreams it can only be read once. If you read + * the response though some other method like getContentAsBytes() + * or getBodyAsString() it will read this method and consume the InputStream + * + * @return the content + */ + InputStream getContent(); + + /** + * Returns the body as bytes. This consumes the entire InputStream. + * Warning: Calling this on very large responses will place all data in + * memory and could create OutOfMemory errors + * + * @return the content as bytes + */ + byte[] getContentAsBytes(); + + /** + * Returns the body as UTF-8 String. This consumes the entire InputStream. + * Warning: Calling this on very large responses will place all data in + * memory and could create OutOfMemory errors + * + * Using this method with a binary response will make you sad + * + * @return the content as a UTF-8 String + */ + String getContentAsString(); + + /** + * Returns the body as UTF-8 String. This consumes the entire InputStream. + * Warning: Calling this on very large responses will place all data in + * memory and could create OutOfMemory errors + * + * Using this method with a binary response will make you sad + * + * @param charset the charset for the String + * @return the content as a string in the provided charset. + */ + String getContentAsString(String charset); + + /** + * Returns the body content of the response as a InputStreamReader. + * Like most InputStreams it can only be read once. If you read + * the response though some other method like getContentAsBytes() + * or getBodyAsString() it will read this method and consume the InputStream + * + * @return the content + */ + InputStreamReader getContentReader(); + + /** + * Indicates that the response has content + * @return boolean indicating that the response has content. + */ + boolean hasContent(); + + /** + * Returns the mime type of the response content as indicated by + * the Content-Type header or a empty string if none is supplied + * (e.g. application/json) + * @return the Content-Type + */ + String getContentType(); + + /** + * Returns the encoding of the response as indicated by the Content-Encoding header + * or returns a empty string if none provided. + * + * @return the encoding + */ + String getEncoding(); + + /** + * Returns the current config for this request/response + * @return the config + */ + Config getConfig(); + + /** + * returns a lightweight read only summary of the request. + * + * @return the request summary + */ + HttpRequestSummary getRequestSummary(); + + /** + * returns a lightweight read only summary of the response. + * @return the response summary + */ + HttpResponseSummary toSummary(); +} diff --git a/unirest/src/main/java/kong/unirest/core/RawResponseBase.java b/unirest/src/main/java/kong/unirest/core/RawResponseBase.java new file mode 100644 index 000000000..0af0c0887 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/RawResponseBase.java @@ -0,0 +1,83 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public abstract class RawResponseBase implements RawResponse { + + private static final Pattern CHARSET_PATTERN = Pattern.compile("(?i)\\bcharset=\\s*\"?([^\\s;\"]*)"); + private final HttpRequestSummary reqSummary; + protected Config config; + + protected RawResponseBase(Config config, HttpRequestSummary summary){ + this.config = config; + this.reqSummary = summary; + } + + protected String getCharSet() { + String contentType = getContentType(); + String responseCharset = getCharsetFromContentType(contentType); + if (responseCharset != null && !responseCharset.trim().equals("")) { + return responseCharset; + } + return config.getDefaultResponseEncoding(); + } + + /** + * Parse out a charset from a content type header. + * + * @param contentType e.g. "text/html; charset=EUC-JP" + * @return "EUC-JP", or null if not found. Charset is trimmed and uppercased. + */ + private String getCharsetFromContentType(String contentType) { + if (contentType == null) { + return null; + } + + Matcher m = CHARSET_PATTERN.matcher(contentType); + if (m.find()) { + return m.group(1).trim().toUpperCase(); + } + return null; + } + + @Override + public Config getConfig() { + return config; + } + + @Override + public HttpResponseSummary toSummary() { + return new ResponseSummary(this); + } + + @Override + public HttpRequestSummary getRequestSummary() { + return reqSummary; + } +} diff --git a/unirest/src/main/java/kong/unirest/core/RequestBodyEntity.java b/unirest/src/main/java/kong/unirest/core/RequestBodyEntity.java new file mode 100644 index 000000000..30bf618cd --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/RequestBodyEntity.java @@ -0,0 +1,109 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import kong.unirest.core.json.JSONElement; + +import java.io.InputStream; +import java.nio.charset.Charset; + +public interface RequestBodyEntity extends HttpRequest, Body { + /** + * Set a byte array as the body of the request + * @param bodyBytes the byte[] + * @return this request builder + */ + RequestBodyEntity body(byte[] bodyBytes); + + /** + * Set a String as the body of the request + * @param bodyAsString the String + * @return this request builder + */ + RequestBodyEntity body(String bodyAsString); + + /** + * Set JSON on the body + * @param jsonBody the JsonNode + * @return this request builder + */ + RequestBodyEntity body(JsonNode jsonBody); + + /** + * Set a InputStream as the body + * @param body the Object + * @return this request builder + */ + RequestBodyEntity body(InputStream body); + + /** + * Set JSON on the body + * @param body the JSONElement + * @return this request builder + */ + RequestBodyEntity body(JSONElement body); + + /** + * Set a Object as the body of the request. This will be serialized with one of the following methods: + * - Strings are native + * - JSONElements use their native toString + * - Everything else will pass through the supplied ObjectMapper + * @param body the Object + * @return this request builder + */ + RequestBodyEntity body(Object body); + + /** + * Set the Charset encoding for the Content-Type. This is appended to the Content-Type Header + * (e.g. application/x-www-form-urlencoded; charset=US-ASCII) + * Default is UTF-8 + * @param charset the charset + * @return this request builder + */ + RequestBodyEntity charset(Charset charset); + + /** + * Removes any Charset for the Content-Type for when servers cannot process it. + * (e.g. application/x-www-form-urlencoded) + * @return this request builder + */ + default RequestBodyEntity noCharset() { + return charset(null); + } + + /** + * @param type The content mime type + * @return this request builder + */ + RequestBodyEntity contentType(String type); + + /** + * Set a Progress upload monitor suitable for drawing progress bars and whatnot. Works With + * @param monitor a monitor + * @return The same MultipartBody + * */ + RequestBodyEntity uploadMonitor(ProgressMonitor monitor); +} diff --git a/unirest/src/main/java/kong/unirest/core/RequestFactory.java b/unirest/src/main/java/kong/unirest/core/RequestFactory.java new file mode 100644 index 000000000..11fffc742 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/RequestFactory.java @@ -0,0 +1,48 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +class RequestFactory { + public static R copy(HttpRequest baseRequest) { + if(baseRequest instanceof HttpRequestNoBody){ + return (R) new HttpRequestNoBody((HttpRequestNoBody)baseRequest); + } + if(baseRequest instanceof HttpRequestBody){ + return (R) new HttpRequestBody((HttpRequestBody)baseRequest); + } + if(baseRequest instanceof HttpRequestUniBody){ + return (R) new HttpRequestUniBody((HttpRequestUniBody)baseRequest); + } + if(baseRequest instanceof HttpRequestMultiPart) { + return (R) new HttpRequestMultiPart((HttpRequestMultiPart)baseRequest); + } + if(baseRequest instanceof HttpRequestJsonPatch) { + return (R)new HttpRequestJsonPatch((HttpRequestJsonPatch)baseRequest); + } + + throw new UnirestException("Cannot find matching type: " + baseRequest.getClass()); + } +} diff --git a/unirest/src/main/java/kong/unirest/core/RequestSummary.java b/unirest/src/main/java/kong/unirest/core/RequestSummary.java new file mode 100644 index 000000000..ef4d91720 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/RequestSummary.java @@ -0,0 +1,64 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.util.Collection; +import java.util.List; + +class RequestSummary implements HttpRequestSummary { + private static final SummaryFormatter FORMATTER = new SummaryFormatter(); + private final BaseRequest request; + + + RequestSummary(BaseRequest request) { + this.request = request; + } + + @Override + public HttpMethod getHttpMethod() { + return request.getHttpMethod(); + } + + @Override + public String getUrl() { + return request.getUrl(); + } + + @Override + public String getRawPath() { + return request.getPath().rawPath(); + } + + @Override + public String asString() { + return FORMATTER.apply(request); + } + + @Override + public Collection
getHeaders() { + return List.copyOf(request.getHeaders().all()); + } +} diff --git a/unirest/src/main/java/kong/unirest/core/ResponseSummary.java b/unirest/src/main/java/kong/unirest/core/ResponseSummary.java new file mode 100644 index 000000000..cbe6150cc --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/ResponseSummary.java @@ -0,0 +1,46 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +class ResponseSummary implements HttpResponseSummary { + private final int status; + private final String statusText; + + ResponseSummary(RawResponse response) { + this.status = response.getStatus(); + this.statusText = response.getStatusText(); + } + + @Override + public int getStatus() { + return status; + } + + @Override + public String getStatusText() { + return statusText; + } +} diff --git a/unirest/src/main/java/kong/unirest/core/RetryStrategy.java b/unirest/src/main/java/kong/unirest/core/RetryStrategy.java new file mode 100644 index 000000000..ff5da30a8 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/RetryStrategy.java @@ -0,0 +1,139 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.time.Instant; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +/** + * A Strategy for performing retries. + * Comes with a standard implementation which follows spec + */ +public interface RetryStrategy { + /** + * Checks to see if the response is retryable + * @param response the last response + * @return a bool indicating if the request should be retried + */ + boolean isRetryable(HttpResponse response); + + /** + * Get the number of milliseconds the system should wait before retrying. + * A value less than 1 will result in the termination of the retry loop + * @param response the last response + * @return millies + */ + long getWaitTime(HttpResponse response); + + /** + * Get the max number of times the Unirest should retry responses before giving up and allowing a final return + * @return the max attempts + */ + int getMaxAttempts(); + + /** + * Puts the current executing thread to sleep for the specified millis + * @param millies sleepy time millies + */ + default void waitFor(long millies) { + try { + TimeUnit.MILLISECONDS.sleep(millies); + } catch (InterruptedException e) { + throw new UnirestException(e); + } + } + + /** + * A standard implementation of the RetryStrategy which follows spec based Retry-After logic + * - Will attempt a retry on any 301, 429, 503, or 529 response which is accompanied by a Retry-After header. + * - Retry-After can be either date or seconds based + * see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After + */ + class Standard implements RetryStrategy { + private static final Set RETRY_CODES = Set.of(301, 429, 503, 529); + private static final String RETRY_AFTER = "Retry-After"; + private final int maxAttempts; + + public Standard(int maxAttempts){ + this.maxAttempts = maxAttempts; + } + + @Override + public boolean isRetryable(HttpResponse response) { + return response != null && RETRY_CODES.contains(response.getStatus()) + && response.getHeaders().containsKey(RETRY_AFTER); + } + + @Override + public long getWaitTime(HttpResponse response) { + if(response == null){ + return 0; + } + String value = response.getHeaders().getFirst(RETRY_AFTER); + return parseToMillies(value); + } + + protected Long parseToMillies(String value) { + return trySeconds(value) + .orElseGet(() -> tryAsDateTime(value)); + } + + @Override + public int getMaxAttempts() { + return maxAttempts; + } + + private static Long tryAsDateTime(String value) { + ZonedDateTime zdt = Util.tryParseToDate(value); + if(zdt == null){ + return 0L; + } + Instant now = Util.now(); + return ChronoUnit.MILLIS.between(now, zdt.toInstant()); + } + + private static Optional trySeconds(String s){ + try{ + long seconds = parse(s); + return Optional.of(seconds); + }catch (NumberFormatException e){ + return Optional.empty(); + } + } + + private static long parse(String s) { + if(s.contains(".")){ + double d = Double.parseDouble(s); + return Math.round(d * 1000); + } + return Long.parseLong(s) * 1000; + } + } +} diff --git a/unirest/src/main/java/kong/unirest/core/SseHandler.java b/unirest/src/main/java/kong/unirest/core/SseHandler.java new file mode 100644 index 000000000..58d4c2308 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/SseHandler.java @@ -0,0 +1,48 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import kong.unirest.core.java.Event; + +/** + * The Server Sent Event Handler is used to consume Server Sent Events + * https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events + * + */ +@FunctionalInterface +public interface SseHandler { + /** + * Dispatched event + * @param event the event + */ + void onEvent(Event event); + + /** + * comments are piped to this method and then immediately thrown away + * @param line the comment + */ + default void onComment(String line) {} +} diff --git a/unirest/src/main/java/kong/unirest/core/SseRequest.java b/unirest/src/main/java/kong/unirest/core/SseRequest.java new file mode 100644 index 000000000..90bfde2b1 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/SseRequest.java @@ -0,0 +1,184 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import kong.unirest.core.java.Event; + +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Stream; + +/** + * The Server Sent Event Request Builder interface + */ +public interface SseRequest { + /** + * add a route param that replaces the matching {name} + * For example routeParam("name", "fred") will replace {name} in + * https://localhost/users/{user} + * to + * https://localhost/users/fred + * + * @param name the name of the param (do not include curly braces {} + * @param value the value to replace the placeholder with + * @return this request builder + */ + SseRequest routeParam(String name, String value); + + /** + * add a route param map that replaces the matching {name} + * For example routeParam(Map.of("name", "fred")) will replace {name} in + * https://localhost/users/{user} + * to + * https://localhost/users/fred + * + * @param params a map of path params + * @return this request builder + */ + SseRequest routeParam(Map params); + + /** + * Basic auth credentials + * @param username the username + * @param password the password + * @return this request builder + */ + SseRequest basicAuth(String username, String password); + + /** + * The Accept header to send. + * The default (and standard) is "text/event-stream" + * @param value a valid mime type for the Accept header + * @return this request builder + */ + SseRequest accept(String value); + + /** + * Add a http header, HTTP supports multiple of the same header. This will continue to append new values + * @param name name of the header + * @param value value for the header + * @return this request builder + */ + SseRequest header(String name, String value); + + /** + * Replace a header value or add it if it doesn't exist + * @param name name of the header + * @param value value for the header + * @return this request builder + */ + SseRequest headerReplace(String name, String value); + + /** + * Add headers as a map + * @param headerMap a map of headers + * @return this request builder + */ + SseRequest headers(Map headerMap); + + /** + * Add a simple cookie header + * @param name the name of the cookie + * @param value the value of the cookie + * @return this request builder + */ + SseRequest cookie(String name, String value); + + /** + * Add a simple cookie header + * @param cookie a cookie + * @return this request builder + */ + SseRequest cookie(Cookie cookie); + + /** + * Add a collection of cookie headers + * @param cookies a cookie + * @return this request builder + */ + SseRequest cookie(Collection cookies); + + /** + * add a query param to the url. The value will be URL-Encoded + * @param name the name of the param + * @param value the value of the param + * @return this request builder + */ + SseRequest queryString(String name, Object value); + + /** + * Add multiple param with the same param name. + * queryString("name", Arrays.asList("bob", "linda")) will result in + * ?name=bob&name=linda + * @param name the name of the param + * @param value a collection of values + * @return this request builder + */ + SseRequest queryString(String name, Collection value); + + /** + * Add query params as a map of name value pairs + * @param parameters a map of params + * @return this request builder + */ + SseRequest queryString(Map parameters); + + /** + * @return the headers for the request + */ + Headers getHeaders(); + + /** + * Sets the Last-Event-ID HTTP request header reports an EventSource object's last event ID string to the server when the user agent is to reestablish the connection. + * @param id the ID + * @return this request builder + */ + SseRequest lastEventId(String id); + + /** + * @return the full URL if the request + */ + String getUrl(); + + /** + * execute a SSE Event connection async. + * Because these events are a stream they are processed async and take a handler you can use to consume the events + * @param handler the SseHandler + * @return a CompletableFuture which can be used to monitor if the task is complete or not + */ + CompletableFuture connect(SseHandler handler); + + /** + * execute a synchronous Server Sent Event Stream + * Due to the nature of SSE this stream may remain open indefinitely + * meaning you may be blocked while attempting to collect the stream. + * It is recommend you consume the stream rather than doing any action that requires + * it to stop. + * @return a stream of events + */ + Stream connect(); +} diff --git a/unirest/src/main/java/kong/unirest/core/SseRequestImpl.java b/unirest/src/main/java/kong/unirest/core/SseRequestImpl.java new file mode 100644 index 000000000..ae4eab12d --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/SseRequestImpl.java @@ -0,0 +1,154 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import kong.unirest.core.java.Event; + +import java.util.Collection; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Stream; + +public class SseRequestImpl implements SseRequest { + private final Config config; + private final Path url; + protected Headers headers = new Headers(); + + + + public SseRequestImpl(Config config, String url) { + Objects.requireNonNull(config, "Config cannot be null"); + Objects.requireNonNull(url, "URL cannot be null"); + + this.config = config; + this.url = new Path(url, config.getDefaultBaseUrl()); + headers.putAll(config.getDefaultHeaders()); + } + + @Override + public SseRequest routeParam(String name, String value) { + url.param(name, value); + return this; + } + + @Override + public SseRequest routeParam(Map params) { + url.param(params); + return this; + } + + @Override + public SseRequest basicAuth(String username, String password) { + headers.setBasicAuth(username, password); + return this; + } + + @Override + public SseRequest accept(String value) { + headers.accepts(value); + return this; + } + + @Override + public SseRequest header(String name, String value) { + headers.add(name, value); + return this; + } + + @Override + public SseRequest headerReplace(String name, String value) { + headers.replace(name, value); + return this; + } + + @Override + public SseRequest headers(Map headerMap) { + headers.add(headerMap); + return this; + } + + @Override + public SseRequest cookie(String name, String value) { + headers.cookie(new Cookie(name, value)); + return this; + } + + @Override + public SseRequest cookie(Cookie cookie) { + headers.cookie(cookie); + return this; + } + + @Override + public SseRequest cookie(Collection cookies) { + headers.cookie(cookies); + return this; + } + + @Override + public SseRequest queryString(String name, Object value) { + url.queryString(name, value); + return this; + } + + @Override + public SseRequest queryString(String name, Collection value) { + url.queryString(name, value); + return this; + } + + @Override + public SseRequest queryString(Map parameters) { + url.queryString(parameters); + return this; + } + + @Override + public SseRequest lastEventId(String id) { + return header("Last-Event-ID", id); + } + + @Override + public CompletableFuture connect(SseHandler handler) { + return config.getClient().sse(this, handler); + } + + @Override + public Stream connect() { + return config.getClient().sse(this); + } + + @Override + public Headers getHeaders() { + return headers; + } + + @Override + public String getUrl() { + return url.toString(); + } +} diff --git a/unirest/src/main/java/kong/unirest/core/StringResponse.java b/unirest/src/main/java/kong/unirest/core/StringResponse.java new file mode 100644 index 000000000..9ae26f816 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/StringResponse.java @@ -0,0 +1,45 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +public class StringResponse extends BaseResponse { + private String body; + + public StringResponse(RawResponse response, String encoding) { + super(response); + body = response.getContentAsString(encoding); + } + + @Override + public String getBody() { + return body; + } + + @Override + protected String getRawBody() { + return body; + } +} diff --git a/unirest/src/main/java/kong/unirest/core/SummaryFormatter.java b/unirest/src/main/java/kong/unirest/core/SummaryFormatter.java new file mode 100644 index 000000000..b79952838 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/SummaryFormatter.java @@ -0,0 +1,91 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.util.StringJoiner; +import java.util.function.Function; + +class SummaryFormatter implements Function, String> { + + @Override + public String apply(HttpRequest req) { + StringJoiner sb = new StringJoiner(System.lineSeparator()); + sb.add(req.getHttpMethod().name() + " " + req.getUrl()); + req.getHeaders().all().forEach(h -> sb.add(h.getName() + ": " + h.getValue())); + req.getBody().ifPresent(body -> { + if(!req.getHeaders().containsKey("content-type") && body.isMultiPart()){ + sb.add(String.format("Content-Type: multipart/form-data; boundary=%s;charset=%s\"", body.getBoundary(), body.getCharset())); + } + }); + + sb.add("==================================="); + addBody(req, sb); + return sb.toString(); + } + + private void addBody(HttpRequest req, StringJoiner sb) { + req.getBody().ifPresent(b -> { + if(b.isEntityBody()){ + sb.add(String.valueOf(b.uniPart().getValue())); + } else if (b.isMultiPart()) { + toMultiPartAproximation(b, sb); + } else { + Path path = new Path("/"); + b.multiParts().stream().filter(p -> !p.isFile()).forEach(p -> { + path.queryString(p.getName(), p.getValue()); + }); + sb.add(path.getQueryString()); + } + }); + } + + private String toMultiPartAproximation(Body b, StringJoiner sj) { + b.multiParts().forEach(p -> { + sj.add("--"+b.getBoundary()); + if(p.isFile()){ + sj.add(String.format("Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"", p.getName(), p.getFileName())); + sj.add("Content-Type: " + p.getContentType()); + addExtraHeaders(p, sj); + sj.add(""); + } else { + sj.add("Content-Disposition: form-data; name:\""+p.getName()+"\""); + sj.add("Content-Type: " + p.getContentType()); + addExtraHeaders(p, sj); + sj.add(String.valueOf(p.getValue())); + } + sj.add(""); + }); + return sj.toString(); + } + + private void addExtraHeaders(BodyPart p, StringJoiner sj) { + if(p.getHeaders() != null){ + p.getHeaders().all().forEach(h -> { + sj.add(h.getName() + ": " + h.getValue()); + }); + } + } +} diff --git a/unirest/src/main/java/kong/unirest/core/TestBody.java b/unirest/src/main/java/kong/unirest/core/TestBody.java new file mode 100644 index 000000000..3f681c7bc --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/TestBody.java @@ -0,0 +1,54 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +public class TestBody implements Body { + + private final List parts; + + public TestBody(BodyPart... parts){ + this.parts = Arrays.asList(parts); + } + + @Override + public Collection multiParts() { + return parts; + } + + @Override + public boolean isMultiPart() { + return true; + } + + @Override + public boolean isEntityBody() { + return false; + } +} diff --git a/unirest/src/main/java/kong/unirest/core/UniByteArrayBody.java b/unirest/src/main/java/kong/unirest/core/UniByteArrayBody.java new file mode 100644 index 000000000..24eb899dc --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/UniByteArrayBody.java @@ -0,0 +1,42 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +class UniByteArrayBody extends BodyPart { + UniByteArrayBody(byte[] bodyBytes) { + super(bodyBytes, null, null); + } + + @Override + public boolean isFile() { + return false; + } + + @Override + public String toString() { + return String.format("[binary data length=%s]", getValue().length); + } +} diff --git a/unirest/src/main/java/kong/unirest/core/UniMetric.java b/unirest/src/main/java/kong/unirest/core/UniMetric.java new file mode 100644 index 000000000..c48fec7b1 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/UniMetric.java @@ -0,0 +1,39 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +/** + * A UniMetric is a factory for producing a MetricContext this will be called just before + * any Http Request + */ +public interface UniMetric { + /** + * Factory method for a MetricContext + * @param request A summary of the current Http Request about to be performed + * @return a MetricContext for this request + */ + MetricContext begin(HttpRequestSummary request); +} diff --git a/unirest/src/main/java/kong/unirest/core/UnibodyString.java b/unirest/src/main/java/kong/unirest/core/UnibodyString.java new file mode 100644 index 000000000..adcd7fcf7 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/UnibodyString.java @@ -0,0 +1,42 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +class UnibodyString extends BodyPart { + UnibodyString(String bodyAsString) { + super(bodyAsString, null, null); + } + + @Override + public boolean isFile() { + return false; + } + + @Override + public String toString() { + return getValue(); + } +} diff --git a/unirest/src/main/java/kong/unirest/core/Unirest.java b/unirest/src/main/java/kong/unirest/core/Unirest.java new file mode 100644 index 000000000..0e8252886 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/Unirest.java @@ -0,0 +1,201 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +/** + * Static entry point for the primary Unirest client. + *

+ * Use this facade to access the default shared {@link UnirestInstance}, configure + * the global client, create requests, or spawn isolated instances when you need + * separate configuration. + *

+ * + *

Typical usage

+ *
{@code
+ * Unirest.config().defaultBaseUrl("https://api.example.com");
+ * String body = Unirest.get("/status").asString().getBody();
+ * }
+ * + * @see UnirestInstance + * @see Config + */ +public class Unirest { + + private static UnirestInstance primaryInstance = new UnirestInstance(new Config()); + + /** + * Access the default configuration for the primary Unirest instance. + * @return the config object of the primary instance + */ + public static Config config() { + return primaryInstance.config(); + } + + /** + * Close the asynchronous client and its event loop. Use this method to close all the threads and allow an application to exit. + * This will also clear any options returning Unirest to a default state + */ + public static void shutDown() { + shutDown(true); + } + + /** + * Close the asynchronous client and its event loop. Use this method to close all the threads and allow an application to exit. + * @param clearOptions indicates if options should be cleared. Note that the HttpClient, AsyncClient and thread monitors will not be retained after shutDown. + */ + public static void shutDown(boolean clearOptions) { + primaryInstance.reset(clearOptions); + } + + /** + * Start a GET HttpRequest which does not support a body from the primary config + * @param url the endpoint to access. Can include placeholders for path params using curly braces {} + * @return A HttpRequest builder + */ + public static GetRequest get(String url) { + return primaryInstance.get(url); + } + + /** + * Start a HEAD HttpRequest which does not support a body from the primary config + * @param url the endpoint to access. Can include placeholders for path params using curly braces {} + * @return A HttpRequest builder + */ + public static GetRequest head(String url) { + return primaryInstance.head(url); + } + + /** + * Start a OPTIONS HttpRequest which does not support a body from the primary config + * @param url the endpoint to access. Can include placeholders for path params using curly braces {} + * @return A HttpRequest builder + */ + public static GetRequest options(String url) { + return primaryInstance.options(url); + } + + /** + * Start a POST HttpRequest which supports a body from the primary config + * @param url the endpoint to access. Can include placeholders for path params using curly braces {} + * @return A HttpRequest builder + */ + public static HttpRequestWithBody post(String url) { + return primaryInstance.post(url); + } + + /** + * Start a DELETE HttpRequest which supports a body from the primary config + * @param url the endpoint to access. Can include placeholders for path params using curly braces {} + * @return A HttpRequest builder + */ + public static HttpRequestWithBody delete(String url) { + return primaryInstance.delete(url); + } + + /** + * Start a PATCH HttpRequest which supports a body from the primary config + * @param url the endpoint to access. Can include placeholders for path params using curly braces {} + * @return A HttpRequest builder + */ + public static HttpRequestWithBody patch(String url) { + return primaryInstance.patch(url); + } + + /** + * Start a PUT HttpRequest which supports a body from the primary config + * @param url the endpoint to access. Can include placeholders for path params using curly braces {} + * @return A HttpRequest builder + */ + public static HttpRequestWithBody put(String url) { + return primaryInstance.put(url); + } + + /** + * Start a QUERY HttpRequest from the primary config. + * @param url the endpoint to access. Can include placeholders for path params using curly braces {} + * @return A HttpRequest builder + */ + public static HttpRequestWithBody query(String url){ + return primaryInstance.query(url); + } + + /** + * Start a PATCH HttpRequest which supports a JSON Patch builder. + * this supports RFC-6902 https://tools.ietf.org/html/rfc6902 + * @param url the endpoint to access. Can include placeholders for path params using curly braces {} + * @return A HttpRequest builder + */ + public static JsonPatchRequest jsonPatch(String url) { + return primaryInstance.jsonPatch(url); + } + + /** + * Start an HttpRequest for the given HTTP method from the primary config. + * @param method the HTTP method name + * @param url the endpoint to access. Can include placeholders for path params using curly braces {} + * @return A HttpRequest builder + */ + public static HttpRequestWithBody request(String method, String url) { + return primaryInstance.request(method, url); + } + + /** + * Start a WebSocket request from the primary config. + * @param url the endpoint to access + * @return a WebSocket request builder + */ + public static WebSocketRequest webSocket(String url) { + return primaryInstance.webSocket(url); + } + + /** + * Start a Server-Sent Events request from the primary config. + * @param url the endpoint to access + * @return an SSE request builder + */ + public static SseRequest sse(String url) { + return primaryInstance.sse(url); + } + + /** + * Spawn a new Unirest Instance with a new config. + * Don't forget to shut it down when your done. + * It will not be tracked and shutdown with Unirest.shutDown() + * + * @return a new UnirestInstance + */ + public static UnirestInstance spawnInstance() { + return new UnirestInstance(new Config()); + } + + /** + * Return the shared primary {@link UnirestInstance}. + * @return the primary instance + */ + public static UnirestInstance primaryInstance() { + return primaryInstance; + } +} diff --git a/unirest/src/main/java/kong/unirest/core/UnirestConfigException.java b/unirest/src/main/java/kong/unirest/core/UnirestConfigException.java new file mode 100644 index 000000000..c1a28287a --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/UnirestConfigException.java @@ -0,0 +1,36 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +public class UnirestConfigException extends UnirestException { + public UnirestConfigException(Exception e){ + super(e); + } + + public UnirestConfigException(String msg) { + super(msg); + } +} diff --git a/unirest/src/main/java/kong/unirest/core/UnirestException.java b/unirest/src/main/java/kong/unirest/core/UnirestException.java new file mode 100644 index 000000000..8cf243f6a --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/UnirestException.java @@ -0,0 +1,54 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.util.Collection; +import java.util.stream.Collectors; + +public class UnirestException extends RuntimeException { + + private static final long serialVersionUID = -3714840499934575734L; + + public UnirestException(Exception e) { + super(e); + } + + public UnirestException(String msg) { + super(msg); + } + + public UnirestException(Throwable ex) { + super(ex); + } + + public UnirestException(String message, Throwable e) { + super(message, e); + } + + public UnirestException(Collection ex) { + super(ex.stream().map(e -> e.getClass().getName() + " " + e.getMessage()).collect(Collectors.joining("\n"))); + } +} diff --git a/unirest/src/main/java/kong/unirest/core/UnirestInstance.java b/unirest/src/main/java/kong/unirest/core/UnirestInstance.java new file mode 100644 index 000000000..3a17696bd --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/UnirestInstance.java @@ -0,0 +1,188 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +/** + * A Instance of the unirest runtime, you can have many of these but it is the config + * and its clients where all the action happens. + * This class is suitable for mocking. + */ +public class UnirestInstance implements AutoCloseable { + + private final Config config; + + /** + * Create a new UnirestInstance with a config + * @param config the config for the instance + */ + public UnirestInstance(Config config){ + this.config = config; + } + /** + * Access the default configuration for the primary Unirest instance. + * @return the instance's config + */ + public Config config() { + return config; + } + + /** + * Close the asynchronous client and its event loop. Use this method to close all the threads and allow an application to exit. + * This will also clear any options returning Unirest to a default state + */ + public void reset() { + reset(true); + } + + /** + * Close the asynchronous client and its event loop. Use this method to close all the threads and allow an application to exit. + * + * @param clearOptions indicates if options should be cleared. Note that the HttpClient, AsyncClient and thread monitors will not be retained after shutDown. + */ + public void reset(boolean clearOptions) { + config.reset(clearOptions); + } + + /** + * Start a GET HttpRequest which does not support a body from the primary config + * @param url the endpoint to access. Can include placeholders for path params using curly braces {} + * @return A HttpRequest builder + */ + public GetRequest get(String url) { + return new HttpRequestNoBody(config, HttpMethod.GET, url); + } + + /** + * Start a HEAD HttpRequest which does not support a body from the primary config + * @param url the endpoint to access. Can include placeholders for path params using curly braces {} + * @return A HttpRequest builder + */ + public GetRequest head(String url) { + return new HttpRequestNoBody(config, HttpMethod.HEAD, url); + } + + /** + * Start a OPTIONS HttpRequest which does not support a body from the primary config + * @param url the endpoint to access. Can include placeholders for path params using curly braces {} + * @return A HttpRequest builder + */ + public GetRequest options(String url) { + return new HttpRequestNoBody(config, HttpMethod.OPTIONS, url); + } + + /** + * Start a POST HttpRequest which supports a body from the primary config + * @param url the endpoint to access. Can include placeholders for path params using curly braces {} + * @return A HttpRequest builder + */ + public HttpRequestWithBody post(String url) { + return new HttpRequestBody(config, HttpMethod.POST, url); + } + + /** + * Start a QUERY HttpRequest from this instance. + * @param url the endpoint to access. Can include placeholders for path params using curly braces {} + * @return a HttpRequest builder + */ + public HttpRequestWithBody query(String url) { + return new HttpRequestBody(config, HttpMethod.QUERY, url); + } + + /** + * Start a DELETE HttpRequest which supports a body from the primary config + * @param url the endpoint to access. Can include placeholders for path params using curly braces {} + * @return A HttpRequest builder + */ + public HttpRequestWithBody delete(String url) { + return new HttpRequestBody(config, HttpMethod.DELETE, url); + } + + /** + * Start a PATCH HttpRequest which supports a body from the primary config + * @param url the endpoint to access. Can include placeholders for path params using curly braces {} + * @return A HttpRequest builder + */ + public HttpRequestWithBody patch(String url) { + return new HttpRequestBody(config, HttpMethod.PATCH, url); + } + + /** + * Start a PUT HttpRequest which supports a body from the primary config + * @param url the endpoint to access. Can include placeholders for path params using curly braces {} + * @return A HttpRequest builder + */ + public HttpRequestWithBody put(String url) { + return new HttpRequestBody(config, HttpMethod.PUT, url); + } + + /** + * Start a PATCH HttpRequest which supports a JSON Patch builder. + * this supports RFC-6902 https://tools.ietf.org/html/rfc6902 + * @param url the endpoint to access. Can include placeholders for path params using curly braces {} + * @return A HttpRequest builder + */ + public JsonPatchRequest jsonPatch(String url) { + return new HttpRequestJsonPatch(config, url); + } + + /** + * Start an HttpRequest for the given HTTP method. + * @param method the HTTP method name + * @param url the endpoint to access. Can include placeholders for path params using curly braces {} + * @return a HttpRequest builder + */ + public HttpRequestWithBody request(String method, String url) { + return new HttpRequestBody(config, HttpMethod.valueOf(method), url); + } + + /** + * Wraps shutdown and will automatically be called when UnirestInstance is + * used with try-with-resource. This will alleviate the need to manually + * call shutDown as it will be done automatically. + */ + @Override + public void close() { + reset(true); + } + + /** + * Start a WebSocket request from this instance. + * @param url the endpoint to access + * @return a WebSocket request builder + */ + public WebSocketRequest webSocket(String url) { + return new WebSocketRequestImpl(config, url); + } + + /** + * Start a Server-Sent Events request from this instance. + * @param url the endpoint to access + * @return an SSE request builder + */ + public SseRequestImpl sse(String url) { + return new SseRequestImpl(config, url); + } +} diff --git a/unirest/src/main/java/kong/unirest/core/UnirestParsingException.java b/unirest/src/main/java/kong/unirest/core/UnirestParsingException.java new file mode 100644 index 000000000..46dc70655 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/UnirestParsingException.java @@ -0,0 +1,39 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +public class UnirestParsingException extends UnirestException { + private final String originalBody; + + public UnirestParsingException(String originalBody, Exception e) { + super(e); + this.originalBody = originalBody; + } + + public String getOriginalBody() { + return originalBody; + } +} diff --git a/unirest/src/main/java/kong/unirest/core/UnrecoverableException.java b/unirest/src/main/java/kong/unirest/core/UnrecoverableException.java new file mode 100644 index 000000000..c928d7f91 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/UnrecoverableException.java @@ -0,0 +1,35 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +/** + * An exception which prevents Unirest from attempting to recover the body from a failed response / parsing error + */ +public class UnrecoverableException extends UnirestException { + public UnrecoverableException(Exception e) { + super(e); + } +} diff --git a/unirest/src/main/java/kong/unirest/core/Util.java b/unirest/src/main/java/kong/unirest/core/Util.java new file mode 100644 index 000000000..91b06c701 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/Util.java @@ -0,0 +1,115 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.time.Instant; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.*; +import java.util.function.Supplier; +import java.util.stream.Stream; + + +public class Util { + public static final DateTimeFormatter DEFAULT_PATTERN = DateTimeFormatter.ofPattern("EEE, dd-MMM-yyyy HH:mm:ss zzz", Locale.US); + static final List FORMATS = Arrays.asList( + DEFAULT_PATTERN, + DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US) + ); + private static Supplier clock = Instant::now; + + static void freezeClock(Instant instant) { + clock = () -> instant; + } + + static void resetClock() { + clock = Instant::now; + } + + static Instant now() { + return clock.get(); + } + + public static Optional tryCast(T original, Class too) { + if (original != null && too.isAssignableFrom(original.getClass())) { + return Optional.of((M) original); + } + return Optional.empty(); + } + + //In Java 9 this has been added as Optional::stream. Remove this whenever we get there. + static Stream stream(Optional opt) { + return opt.map(Stream::of).orElseGet(Stream::empty); + } + + static String nullToEmpty(Object v) { + if (v == null) { + return ""; + } + return String.valueOf(v); + } + + static String encode(String input) { + try { + return URLEncoder.encode(input, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new UnirestException(e); + } + } + + static boolean isNullOrEmpty(String s) { + return s == null || s.trim().isEmpty(); + } + + public static String toBasicAuthValue(String username, String password) { + return "Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes()); + } + + static FileInputStream getFileInputStream(String location) { + try { + return new FileInputStream(location); + } catch (FileNotFoundException e) { + throw new UnirestConfigException(e); + } + } + + public static ZonedDateTime tryParseToDate(String text) { + return FORMATS.stream().map(f -> { + try { + return ZonedDateTime.parse(text, f); + } catch (DateTimeParseException e) { + return null; + } + }).filter(Objects::nonNull) + .findFirst() + .orElse(null); + } +} diff --git a/unirest/src/main/java/kong/unirest/core/WebSocketRequest.java b/unirest/src/main/java/kong/unirest/core/WebSocketRequest.java new file mode 100644 index 000000000..182861932 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/WebSocketRequest.java @@ -0,0 +1,155 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.net.http.WebSocket; +import java.util.Collection; +import java.util.Map; + +public interface WebSocketRequest { + /** + * add a route param that replaces the matching {name} + * For example routeParam("name", "fred") will replace {name} in + * https://localhost/users/{user} + * to + * https://localhost/users/fred + * + * @param name the name of the param (do not include curly braces {} + * @param value the value to replace the placeholder with + * @return this request builder + */ + WebSocketRequest routeParam(String name, String value); + + /** + * add a route param map that replaces the matching {name} + * For example routeParam(Map.of("name", "fred")) will replace {name} in + * https://localhost/users/{user} + * to + * https://localhost/users/fred + * + * @param params a map of path params + * @return this request builder + */ + WebSocketRequest routeParam(Map params); + + /** + * Basic auth credentials + * @param username the username + * @param password the password + * @return this request builder + */ + WebSocketRequest basicAuth(String username, String password); + + /** + * The Accept header to send (e.g. application/json + * @param value a valid mime type for the Accept header + * @return this request builder + */ + WebSocketRequest accept(String value); + + /** + * The encoding to expect the response to be for cases where the server fails to respond with the proper encoding + * @param encoding a valid mime type for the Accept header + * @return this request builder + */ + WebSocketRequest responseEncoding(String encoding); + + /** + * Add a http header, HTTP supports multiple of the same header. This will continue to append new values + * @param name name of the header + * @param value value for the header + * @return this request builder + */ + WebSocketRequest header(String name, String value); + + /** + * Replace a header value or add it if it doesn't exist + * @param name name of the header + * @param value value for the header + * @return this request builder + */ + WebSocketRequest headerReplace(String name, String value); + + /** + * Add headers as a map + * @param headerMap a map of headers + * @return this request builder + */ + WebSocketRequest headers(Map headerMap); + + /** + * Add a simple cookie header + * @param name the name of the cookie + * @param value the value of the cookie + * @return this request builder + */ + WebSocketRequest cookie(String name, String value); + + /** + * Add a simple cookie header + * @param cookie a cookie + * @return this request builder + */ + WebSocketRequest cookie(Cookie cookie); + + /** + * Add a collection of cookie headers + * @param cookies a cookie + * @return this request builder + */ + WebSocketRequest cookie(Collection cookies); + + /** + * add a query param to the url. The value will be URL-Encoded + * @param name the name of the param + * @param value the value of the param + * @return this request builder + */ + WebSocketRequest queryString(String name, Object value); + + /** + * Add multiple param with the same param name. + * queryString("name", Arrays.asList("bob", "linda")) will result in + * ?name=bob&name=linda + * @param name the name of the param + * @param value a collection of values + * @return this request builder + */ + WebSocketRequest queryString(String name, Collection value); + + /** + * Add query params as a map of name value pairs + * @param parameters a map of params + * @return this request builder + */ + WebSocketRequest queryString(Map parameters); + + WebSocketResponse connect(WebSocket.Listener listener); + + Headers getHeaders(); + + String getUrl(); +} diff --git a/unirest/src/main/java/kong/unirest/core/WebSocketRequestImpl.java b/unirest/src/main/java/kong/unirest/core/WebSocketRequestImpl.java new file mode 100644 index 000000000..2d8628a17 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/WebSocketRequestImpl.java @@ -0,0 +1,148 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.net.http.WebSocket; +import java.time.Instant; +import java.util.Collection; +import java.util.Map; +import java.util.Optional; + +public class WebSocketRequestImpl implements WebSocketRequest { + private Instant creation = Util.now(); + private Optional objectMapper = Optional.empty(); + private String responseEncoding; + protected Headers headers = new Headers(); + protected final Config config; + protected HttpMethod method; + protected Path url; + private Integer connectTimeout; + + public WebSocketRequestImpl(Config config, String url) { + this.config = config; + this.url = new Path(url, config.getDefaultBaseUrl()); + headers.putAll(config.getDefaultHeaders()); + } + + @Override + public WebSocketRequest routeParam(String name, String value) { + url.param(name, value); + return this; + } + + @Override + public WebSocketRequest routeParam(Map params) { + url.param(params); + return this; + } + + @Override + public WebSocketRequest basicAuth(String username, String password) { + headers.setBasicAuth(username, password); + return this; + } + + @Override + public WebSocketRequest accept(String value) { + headers.accepts(value); + return this; + } + + @Override + public WebSocketRequest responseEncoding(String encoding) { + this.responseEncoding = encoding; + return this; + } + + @Override + public WebSocketRequest header(String name, String value) { + headers.add(name, value); + return this; + } + + @Override + public WebSocketRequest headerReplace(String name, String value) { + headers.replace(name, value); + return this; + } + + @Override + public WebSocketRequest headers(Map headerMap) { + headers.add(headerMap); + return this; + } + + @Override + public WebSocketRequest cookie(String name, String value) { + headers.cookie(new Cookie(name, value)); + return this; + } + + @Override + public WebSocketRequest cookie(Cookie cookie) { + headers.cookie(cookie); + return this; + } + + @Override + public WebSocketRequest cookie(Collection cookies) { + headers.cookie(cookies); + return this; + } + + @Override + public WebSocketRequest queryString(String name, Object value) { + url.queryString(name, value); + return this; + } + + @Override + public WebSocketRequest queryString(String name, Collection value) { + url.queryString(name, value); + return this; + } + + @Override + public WebSocketRequest queryString(Map parameters) { + url.queryString(parameters); + return this; + } + + @Override + public WebSocketResponse connect(WebSocket.Listener listener) { + return config.getClient().websocket(this, listener); + } + + @Override + public Headers getHeaders() { + return headers; + } + + @Override + public String getUrl() { + return url.toString(); + } +} diff --git a/unirest/src/main/java/kong/unirest/core/WebSocketResponse.java b/unirest/src/main/java/kong/unirest/core/WebSocketResponse.java new file mode 100644 index 000000000..d7ef88399 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/WebSocketResponse.java @@ -0,0 +1,61 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.net.http.WebSocket; +import java.util.concurrent.CompletableFuture; + +/** + * Just a silly little class that holds on to the socket and listener + */ +public class WebSocketResponse { + private final CompletableFuture webSocketFuture; + private final WebSocket.Listener listener; + + /** + * ctor + * @param webSocketFuture the ws future + * @param listener the listener + */ + public WebSocketResponse(CompletableFuture webSocketFuture, WebSocket.Listener listener) { + this.webSocketFuture = webSocketFuture; + this.listener = listener; + } + + /** + * @return the ws future + */ + public CompletableFuture socket(){ + return webSocketFuture; + } + + /** + * @return the listener + */ + public WebSocket.Listener listener(){ + return listener; + } +} diff --git a/unirest/src/main/java/kong/unirest/core/java/BodyBuilder.java b/unirest/src/main/java/kong/unirest/core/java/BodyBuilder.java new file mode 100644 index 000000000..b1afa62e2 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/java/BodyBuilder.java @@ -0,0 +1,199 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core.java; + +import kong.unirest.core.*; + + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.net.http.HttpRequest; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Optional; +import java.util.stream.Collectors; + +class BodyBuilder { + public static final Charset ASCII = StandardCharsets.US_ASCII; + private final kong.unirest.core.HttpRequest request; + + BodyBuilder(kong.unirest.core.HttpRequest request) { + this.request = request; + } + + HttpRequest.BodyPublisher getBody() { + Optional body = request.getBody(); + return body + .map(o -> toPublisher(o)) + .orElseGet(HttpRequest.BodyPublishers::noBody); + } + + private HttpRequest.BodyPublisher toPublisher(Body o) { + if (o.isEntityBody()) { + return mapToUniBody(o); + } else { + return mapToMultipart(o); + } + } + + private java.net.http.HttpRequest.BodyPublisher mapToMultipart(Body o) { + try { + if (o.multiParts().isEmpty()) { + setContentAsFormEncoding(o); + return HttpRequest.BodyPublishers.noBody(); + } + if (!o.isMultiPart()) { + setContentAsFormEncoding(o); + return HttpRequest.BodyPublishers.ofString( + toFormParams(o) + ); + } + + var builder = MultipartBodyPublisher.newBuilder(o.getBoundary()); + o.multiParts().forEach(part -> { + setMultiPart(o, builder, part); + }); + + MultipartBodyPublisher build = builder.build(o.getMonitor()); + request.header("Content-Type", "multipart/form-data; boundary=" + build.boundary() + ";charset=" + o.getCharset()); + return build; + } catch (Exception e) { + throw new UnirestException(e); + } + } + + private void setContentAsFormEncoding(Body o) { + String content = "application/x-www-form-urlencoded"; + if(o.getCharset() != null){ + content = content + "; charset="+o.getCharset().toString(); + } + if(!alreadyHasMultiPartHeader()){ + request.header(HeaderNames.CONTENT_TYPE, content); + } + } + + private boolean alreadyHasMultiPartHeader() { + return request.getHeaders() + .containsKey(HeaderNames.CONTENT_TYPE); + } + + private String toFormParams(Body o) { + return o.multiParts() + .stream() + .filter(p -> p instanceof ParamPart) + .map(p -> (ParamPart) p) + .map(p -> toPair(p, o)) + .collect(Collectors.joining("&")); + } + + private String toPair(ParamPart p, Body o) { + try { + String encoding = o.getCharset() == null ? "UTF-8" : o.getCharset().toString(); + return String.format("%s=%s", URLEncoder.encode(p.getName(), encoding), URLEncoder.encode(p.getValue(), encoding)); + } catch (UnsupportedEncodingException e) { + throw new UnirestException(e); + } + } + + + private void setMultiPart(Body o, MultipartBodyPublisher.Builder builder, BodyPart part) { + String contentType = part.getContentType(); + if (part.isFile()) { + if (part instanceof FilePart) { + try { + builder.filePart(part.getName(), + ((File) part.getValue()).toPath(), + contentType, + part.getHeaders()); + } catch (FileNotFoundException e) { + throw new UnirestException(e); + } + } else if (part instanceof InputStreamPart) { + if (part.getFileName() != null) { + builder.formPart(part.getName(), standardizeName(part, o.getMode()), + new PartPublisher(HttpRequest.BodyPublishers.ofInputStream(() -> (InputStream) part.getValue()), contentType), contentType, part.getHeaders()); + } else { + builder.formPart(part.getName(), + new PartPublisher(HttpRequest.BodyPublishers.ofInputStream(() -> (InputStream) part.getValue()), contentType), contentType, part.getHeaders()); + } + + } else if (part instanceof ByteArrayPart) { + builder.formPart(part.getName(), + standardizeName(part, o.getMode()), + new PartPublisher(HttpRequest.BodyPublishers.ofByteArray((byte[]) part.getValue()), contentType), contentType, part.getHeaders()); + } + } else { + builder.textPart(part.getName(), String.valueOf(part.getValue()), contentType, part.getHeaders()); + } + } + + private String standardizeName(BodyPart part, MultipartMode mode) { + if (mode.equals(MultipartMode.STRICT)) { + return part.getFileName().chars() + .mapToObj(c -> { + if (!ASCII.newEncoder().canEncode((char) c)) { + return '?'; + } + return Character.valueOf((char) c); + }).map(c -> c.toString()) + .collect(Collectors.joining()); + } + return part.getFileName(); + } + + private HttpRequest.BodyPublisher mapToUniBody(Body b) { + BodyPart bodyPart = b.uniPart(); + if (bodyPart == null) { + return HttpRequest.BodyPublishers.noBody(); + } else if (String.class.isAssignableFrom(bodyPart.getPartType())) { + return createStringBody(b, bodyPart); + } else if (InputStream.class.isAssignableFrom(bodyPart.getPartType())) { + return createInputStreamBody(b, bodyPart); + }else { + return HttpRequest.BodyPublishers.ofByteArray((byte[]) bodyPart.getValue()); + } + } + + private HttpRequest.BodyPublisher createInputStreamBody(Body b, BodyPart bodyPart) { + if(b.getMonitor() != null){ + return HttpRequest.BodyPublishers.ofInputStream( + () -> new MonitorWrapper((InputStream) bodyPart.getValue(), b.getMonitor())); + } + return HttpRequest.BodyPublishers.ofInputStream( + () -> (InputStream) bodyPart.getValue()); + } + + private HttpRequest.BodyPublisher createStringBody(Body b, BodyPart bodyPart) { + Charset charset = b.getCharset(); + if (charset == null) { + return HttpRequest.BodyPublishers.ofString((String) bodyPart.getValue()); + } + return HttpRequest.BodyPublishers.ofString((String) bodyPart.getValue(), charset); + } +} diff --git a/unirest/src/main/java/kong/unirest/core/java/BoundaryAppender.java b/unirest/src/main/java/kong/unirest/core/java/BoundaryAppender.java new file mode 100644 index 000000000..ab463c656 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/java/BoundaryAppender.java @@ -0,0 +1,49 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core.java; + +/** Strategy for appending the boundary across parts. */ +enum BoundaryAppender { + FIRST("--", "\r\n"), + MIDDLE("\r\n--", "\r\n"), + LAST("\r\n--", "--\r\n"); + + private final String prefix; + private final String suffix; + + BoundaryAppender(String prefix, String suffix) { + this.prefix = prefix; + this.suffix = suffix; + } + + void append(StringBuilder target, String boundary) { + target.append(prefix).append(boundary).append(suffix); + } + + static BoundaryAppender get(int partIndex, int partsSize) { + return partIndex <= 0 ? FIRST : (partIndex >= partsSize ? LAST : MIDDLE); + } +} diff --git a/unirest/src/main/java/kong/unirest/core/java/Event.java b/unirest/src/main/java/kong/unirest/core/java/Event.java new file mode 100644 index 000000000..1b60574d8 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/java/Event.java @@ -0,0 +1,132 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core.java; + +import kong.unirest.core.Config; +import kong.unirest.core.GenericType; + +import java.util.Objects; + +/** + * A server sent event. + * Generally modeled on the HTML5 EventSource standard + * https://html.spec.whatwg.org/multipage/ + */ +public class Event { + private final Config config; + private final String id; + private final String data; + private final String event; + + /** + * Constructor used by unirest which includes the Unirest config which uis used for event serialization + * @param id The event ID + * @param event the event label. "message" is the default from the server + * @param data the data in the event. This is concatenated from all lines before a dispatch event (a blank line) + * @param config the unirest config used for deserialization + */ + public Event(String id, String event, String data, Config config) { + this.config = config; + this.id = id; + this.event = event; + this.data = data; + } + + public Event(String id, String event, String data){ + this(id, event, data, null); + } + + /** + * The data field for the message. + * When the EventSource receives multiple consecutive lines that begin with data:, it concatenates them, inserting a newline character between each one. + * Trailing newlines are removed. + * @return the data + */ + public String data() { + return data; + } + + /** + * The event ID to set the EventSource object's last event ID value. + * @return the id + */ + public String id() { + return id; + } + + /** + * A string identifying the type of event described. + * If this is specified, an event will be dispatched on the browser to the listener for the specified event name; + * @return the event name + */ + public String event() { + return event; + } + + /** + * Deserialize the data of the event using the Unirest Config's ObjectMapper + * @param responseClass the type of class you want back + * @return an instance of the class + * @param the class type + */ + public T asObject(Class responseClass) { + return config.getObjectMapper().readValue(data, responseClass); + } + + /** + * Deserialize the data of the event using the Unirest Config's ObjectMapper + * This method takes a GenericType which retains Generics information for types lke List<Foo> + * @param genericType the type of class you want back using a generictype object + * @return an instance of the class + * @param the class type + */ + public T asObject(GenericType genericType){ + return config.getObjectMapper().readValue(data, genericType); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Event)) return false; + Event event = (Event) o; + return Objects.equals(id, event.id) + && Objects.equals(data, event.data) + && Objects.equals(this.event, event.event); + } + + @Override + public int hashCode() { + return Objects.hash(id, data, event); + } + + @Override + public String toString() { + return "SseEvent{" + + "data=" + data + + ", field=" + event + + ", id=" + id + + '}'; + } +} diff --git a/unirest/src/main/java/kong/unirest/core/java/JavaClient.java b/unirest/src/main/java/kong/unirest/core/java/JavaClient.java new file mode 100644 index 000000000..db6ad108e --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/java/JavaClient.java @@ -0,0 +1,208 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core.java; + +import kong.unirest.core.*; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.WebSocket; +import java.time.Duration; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import java.util.stream.Stream; + +import static java.net.http.HttpRequest.newBuilder; +import static java.net.http.HttpResponse.BodySubscribers.ofInputStream; +import static kong.unirest.core.HeaderNames.*; + + +public class JavaClient implements Client { + + private final Config config; + private final HttpClient client; + + public JavaClient(Config config) { + this.config = config; + this.client = new JavaClientBuilder().apply(config); + } + + public JavaClient(Config config, HttpClient client){ + this.config = config; + this.client = client; + } + + @Override + public HttpClient getClient() { + return client; + } + + @Override + public HttpResponse request(HttpRequest request, Function> transformer, Class resultType) { + var reqSum = request.toSummary(); + config.getUniInterceptor().onRequest(request, config); + var requestObj = getRequest(request); + var metric = config.getMetric().begin(reqSum); + try { + var execute = client.send(requestObj, r -> ofInputStream()); + var javaResponse = new JavaResponse(execute, config, reqSum); + metric.complete(javaResponse.toSummary(), null); + var httpResponse = transformBody(transformer, javaResponse); + config.getUniInterceptor().onResponse(httpResponse, reqSum, config); + return httpResponse; + } catch (Exception e) { + metric.complete(null, e); + return (HttpResponse) config.getUniInterceptor().onFail(e, reqSum, config); + } + } + + private java.net.http.HttpRequest getRequest(HttpRequest request) { + try { + var url = URI.create(request.getUrl()); + var jreq = newBuilder(url) + .version(request.getVersion() != null ? request.getVersion() : config.getVersion()) + .method( + request.getHttpMethod().name(), + new BodyBuilder(request).getBody() + ); + + if(!Objects.isNull(request.getRequestTimeout())){ + jreq.timeout(Duration.ofMillis(request.getRequestTimeout())); + } + + setHeaders(request, jreq); + + return jreq.build(); + }catch (RuntimeException e){ + if (e instanceof UnirestException){ + throw e; + } else { + throw new UnirestException(e); + } + } + } + + private void setHeaders(HttpRequest request, java.net.http.HttpRequest.Builder jreq) { + request.getHeaders().all().forEach(h -> jreq.header(h.getName(), h.getValue())); + if (request.getBody().isPresent() && !request.getHeaders().containsKey(CONTENT_TYPE)) { + var value = "text/plain"; + var charset = request.getBody().get().getCharset(); + if (charset != null) { + value = value + "; charset=" + charset; + } + jreq.header(CONTENT_TYPE, value); + } + if(!request.getHeaders().containsKey(CONTENT_ENCODING) && config.isRequestCompressionOn()){ + jreq.header(ACCEPT_ENCODING, "gzip"); + } + } + + + @Override + public CompletableFuture> request(HttpRequest request, Function> transformer, CompletableFuture> callback, Class resultType) { + var reqSum = request.toSummary(); + config.getUniInterceptor().onRequest(request, config); + var requestObj = getRequest(request); + var metric = config.getMetric().begin(reqSum); + + var execute = client.sendAsync(requestObj, + java.net.http.HttpResponse.BodyHandlers.ofInputStream()); + + return execute.thenApplyAsync(h -> { + var t = new JavaResponse(h, config, reqSum); + metric.complete(t.toSummary(), null); + var httpResponse = transformBody(transformer, t); + config.getUniInterceptor().onResponse(httpResponse, reqSum, config); + callback.complete(httpResponse); + return httpResponse; + }).exceptionally(e -> { + var ex = new UnirestException(e); + metric.complete(null, ex); + try { + HttpResponse r = config.getUniInterceptor().onFail(ex, reqSum, config); + callback.complete(r); + return r; + } catch (Exception ee){ + callback.completeExceptionally(e); + } + + return new FailedResponse(ex); + }); + } + + @Override + public WebSocketResponse websocket(WebSocketRequest request, WebSocket.Listener listener) { + var b = client.newWebSocketBuilder(); + request.getHeaders().all().forEach(h -> b.header(h.getName(), h.getValue())); + return new WebSocketResponse(b.buildAsync(URI.create(request.getUrl()), listener), listener); + } + + @Override + public CompletableFuture sse(SseRequest request, SseHandler handler) { + var r = SseRequestBuilder.getHttpRequest(request); + + return client.sendAsync(r, java.net.http.HttpResponse.BodyHandlers.ofLines()) + .thenAccept(new SseResponseHandler(config, handler)) + .exceptionally(ex -> { + System.err.println("Error: " + ex.getMessage()); + return null; + }); + } + + @Override + public Stream sse(SseRequest request) { + + try { + var r = SseRequestBuilder.getHttpRequest(request); + var stream = client.send(r, java.net.http.HttpResponse.BodyHandlers.ofLines()); + var handler = new SseResponseHandler(config, event -> {}); + return handler.map(stream.body()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + protected HttpResponse transformBody(Function> transformer, RawResponse rr) { + try { + return transformer.apply(rr); + }catch (UnrecoverableException ue){ + return new BasicResponse(rr, "", ue); + }catch (RuntimeException e){ + var originalBody = recoverBody(rr); + return new BasicResponse(rr, originalBody, e); + } + } + + private String recoverBody(RawResponse rr){ + try { + return rr.getContentAsString(); + }catch (Exception e){ + return null; + } + } +} diff --git a/unirest/src/main/java/kong/unirest/core/java/JavaClientBuilder.java b/unirest/src/main/java/kong/unirest/core/java/JavaClientBuilder.java new file mode 100644 index 000000000..cfff7b7e6 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/java/JavaClientBuilder.java @@ -0,0 +1,131 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core.java; + +import kong.unirest.core.Config; +import kong.unirest.core.Proxy; +import kong.unirest.core.UnirestConfigException; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.TrustManagerFactory; +import java.net.*; +import java.net.http.HttpClient; +import java.time.Duration; +import java.util.Optional; +import java.util.function.Function; + +class JavaClientBuilder implements Function { + @Override + public HttpClient apply(Config config) { + HttpClient.Builder builder = HttpClient.newBuilder() + .followRedirects(redirectPolicy(config)) + .connectTimeout(Duration.ofMillis(config.getConnectionTimeout())); + configureTLSOptions(config, builder); + + if(config.getVersion() != null){ + builder.version(config.getVersion()); + } + + if(config.getCustomExecutor() != null){ + builder.executor(config.getCustomExecutor()); + } + if(config.getEnabledCookieManagement()){ + builder = builder.cookieHandler(new CookieManager(null, CookiePolicy.ACCEPT_ALL)); + } + if(config.getProxy() != null){ + createProxy(builder, config.getProxy()); + } + if(config.getProxySelector() != null){ + builder = builder.proxy(config.getProxySelector()); + } + if(config.useSystemProperties()){ + builder.proxy(ProxySelector.getDefault()); + } + if(config.getAuthenticator() != null){ + builder.authenticator(config.getAuthenticator()); + } + + return builder.build(); + } + + private void configureTLSOptions(Config config, HttpClient.Builder builder) { + SSLParameters params = new SSLParameters(); + if(!config.isVerifySsl()){ + builder.sslContext(NeverUseInProdTrustManager.create()); + } else if (config.getKeystore() != null){ + builder.sslContext(getSslContext(config)); + } else if(config.getSslContext() != null){ + builder.sslContext(config.getSslContext()); + } + if(config.getProtocols() != null){ + params.setProtocols(config.getProtocols()); + } + if(config.getCiphers() != null){ + params.setCipherSuites(config.getCiphers()); + } + builder.sslParameters(params); + } + + private void createProxy(HttpClient.Builder builder, Proxy proxy) { + InetSocketAddress address = InetSocketAddress.createUnresolved(proxy.getHost(), proxy.getPort()); + builder.proxy(ProxySelector.of(address)); + if(proxy.isAuthenticated()){ + builder.authenticator(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(proxy.getUsername(), proxy.getPassword().toCharArray()); + } + }); + } + } + + private SSLContext getSslContext(Config config) { + try { + TrustManagerFactory tmf = TrustManagerFactory + .getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(config.getKeystore()); + + char[] pass = Optional.ofNullable(config.getKeyStorePassword()) + .map(String::toCharArray) + .orElse(null); + + return SSLContextBuilder.create() + .loadKeyMaterial(config.getKeystore(), pass) + .build(); + }catch (Exception e){ + throw new UnirestConfigException(e); + } + } + + private HttpClient.Redirect redirectPolicy(Config config) { + if(config.getFollowRedirects()){ + return HttpClient.Redirect.NORMAL; + } + return HttpClient.Redirect.NEVER; + } + +} diff --git a/unirest/src/main/java/kong/unirest/core/java/JavaResponse.java b/unirest/src/main/java/kong/unirest/core/java/JavaResponse.java new file mode 100644 index 000000000..883841c97 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/java/JavaResponse.java @@ -0,0 +1,172 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core.java; + +import kong.unirest.core.*; + +import java.io.*; +import java.net.http.HttpResponse; +import java.util.zip.GZIPInputStream; + +import static kong.unirest.core.HeaderNames.CONTENT_TYPE; + +class JavaResponse extends RawResponseBase { + private final HttpResponse response; + + public JavaResponse(HttpResponse response, Config config, HttpRequestSummary summary) { + super(config, summary); + this.response = response; + } + + @Override + public int getStatus() { + return response.statusCode(); + } + + @Override + public String getStatusText() { + return ""; + } + + @Override + public Headers getHeaders() { + Headers h = new Headers(); + response.headers().map() + .entrySet() + .forEach(e -> { + e.getValue().forEach(v -> h.add(e.getKey(), v)); + }); + return h; + } + + @Override + public InputStream getContent() { + try { + InputStream body = response.body(); + if (isGzipped(getEncoding()) && !(body instanceof GZIPInputStream)) { + body = new GZIPInputStream(body); + } + return body; + } catch (EOFException e){ + return new ByteArrayInputStream(new byte[]{}); + } catch (Exception e){ + throw new UnirestException(e); + } + } + + @Override + public byte[] getContentAsBytes() { + if (!hasContent()) { + return new byte[0]; + } + try { + InputStream is = getContent(); + return getBytes(is); + } catch (IOException e2) { + throw new UnirestException(e2); + } + } + + private static byte[] getBytes(InputStream is) throws IOException { + try { + int len; + int size = 1024; + byte[] buf; + + if (is instanceof ByteArrayInputStream) { + size = is.available(); + buf = new byte[size]; + len = is.read(buf, 0, size); + } else { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + buf = new byte[size]; + while ((len = is.read(buf, 0, size)) != -1) { + bos.write(buf, 0, len); + } + buf = bos.toByteArray(); + } + return buf; + } finally { + is.close(); + } + } + + private static boolean isGzipped(String value) { + return "gzip".equalsIgnoreCase(value.toLowerCase().trim()); + } + + @Override + public String getContentAsString() { + return getContentAsString(null); + } + + @Override + public String getContentAsString(String charset) { + if (!hasContent()) { + return ""; + } + try { + String charSet = getCharset(charset); + return new String(getContentAsBytes(), charSet); + } catch (IOException e) { + throw new UnirestException(e); + } + } + + private String getCharset(String charset) { + if (charset == null || charset.trim().isEmpty()) { + return getCharSet(); + } + return charset; + } + + @Override + public InputStreamReader getContentReader() { + return new InputStreamReader(getContent()); + } + + @Override + public boolean hasContent() { + return response.body() != null; + } + + @Override + public String getContentType() { + return response.headers() + .firstValue(CONTENT_TYPE) + .orElse(""); + } + + @Override + public String getEncoding() { + if (hasContent()) { + String s = response.headers().firstValue(HeaderNames.CONTENT_ENCODING) + .orElse(""); + return s; + } + return ""; + } +} diff --git a/unirest/src/main/java/kong/unirest/core/java/MonitorWrapper.java b/unirest/src/main/java/kong/unirest/core/java/MonitorWrapper.java new file mode 100644 index 000000000..e53e1499b --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/java/MonitorWrapper.java @@ -0,0 +1,97 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core.java; + +import kong.unirest.core.ProgressMonitor; + +import java.io.IOException; +import java.io.InputStream; + +class MonitorWrapper extends InputStream { + private final InputStream in; + private volatile long totalNumBytesRead = 0; + + private ProgressMonitor monitor; + + public MonitorWrapper(InputStream value, ProgressMonitor monitor) { + this.in = value; + this.monitor = monitor; + } + + @Override + public int read() throws IOException { + return (int)updateProgress(in.read()); + } + + @Override + public int read(byte[] b) throws IOException { + return (int)updateProgress(in.read(b)); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + return (int)updateProgress(in.read(b, off, len)); + } + + @Override + public long skip(long n) throws IOException { + return updateProgress(in.skip(n)); + } + + @Override + public int available() throws IOException { + return in.available(); + } + + @Override + public void close() throws IOException { + in.close(); + } + + @Override + public synchronized void mark(int readlimit) { + in.mark(readlimit); + } + + @Override + public synchronized void reset() throws IOException { + in.reset(); + } + + @Override + public boolean markSupported() { + return in.markSupported(); + } + + private long updateProgress(long numBytesRead) { + if (numBytesRead > 0) { + this.totalNumBytesRead += numBytesRead; + monitor.accept("body", null, numBytesRead, totalNumBytesRead); + } + + return numBytesRead; + } +} diff --git a/unirest/src/main/java/kong/unirest/core/java/MultipartBodyPublisher.java b/unirest/src/main/java/kong/unirest/core/java/MultipartBodyPublisher.java new file mode 100644 index 000000000..8a683fd08 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/java/MultipartBodyPublisher.java @@ -0,0 +1,229 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core.java; + + +import kong.unirest.core.Headers; +import kong.unirest.core.ProgressMonitor; +import kong.unirest.core.UnirestException; + +import java.io.FileNotFoundException; +import java.net.http.HttpRequest.BodyPublisher; +import java.net.http.HttpRequest.BodyPublishers; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Flow.Subscriber; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Objects.requireNonNull; + +/** + * A {@code BodyPublisher} implementing the multipart request type. + * + * @see RFC 2046 Multipart Media Type + */ +final class MultipartBodyPublisher implements BodyPublisher { + + private static final long UNKNOWN_LENGTH = -1; + private static final long UNINITIALIZED_LENGTH = -2; + + private final List parts; + private final ProgressMonitor monitor; + private final String boundary; + private long contentLength; + + private MultipartBodyPublisher(List parts, ProgressMonitor monitor, String boundary) { + this.parts = parts; + this.monitor = monitor; + this.boundary = boundary; + contentLength = UNINITIALIZED_LENGTH; + } + + String boundary() { + return boundary; + } + + List parts() { + return parts; + } + + @Override + public long contentLength() { + long len = contentLength; + if (len == UNINITIALIZED_LENGTH) { + len = computeLength(); + contentLength = len; + } + return len; + } + + @Override + public void subscribe(Subscriber subscriber) { + requireNonNull(subscriber); + new MultipartSubscription(this.boundary, this.parts, monitor, subscriber).signal(true); + } + + private long computeLength() { + long lengthOfParts = 0L; + String boundary = boundary(); + StringBuilder headings = new StringBuilder(); + for (int i = 0, sz = parts.size(); i < sz; i++) { + Part part = parts.get(i); + long partLength = part.bodyPublisher().contentLength(); + if (partLength < 0) { + return UNKNOWN_LENGTH; + } + lengthOfParts += partLength; + // Append preceding boundary + part header + BoundaryAppender.get(i, sz).append(headings, boundary); + appendPartHeaders(headings, part); + headings.append("\r\n"); + } + BoundaryAppender.LAST.append(headings, boundary); + // Use headings' utf8-encoded length + return lengthOfParts + UTF_8.encode(CharBuffer.wrap(headings)).remaining(); + } + + static void appendPartHeaders(StringBuilder target, Part part) { + part.headers().all().forEach(h -> appendHeader(target, h.getName(), h.getValue())); + } + + private static void appendHeader(StringBuilder target, String name, String value) { + target.append(name).append(": ").append(value).append("\r\n"); + } + + /** Returns a new {@code MultipartBodyPublisher.Builder}. */ + static MultipartBodyPublisher.Builder newBuilder(String boundary) { + return new MultipartBodyPublisher.Builder(boundary); + } + + static final class Builder { + + private final List parts; + private final String boundary; + + Builder(String boundary) { + this.boundary = boundary; + parts = new ArrayList<>(); + } + + /** + * Adds a form field with the given name and body. + * + * @param name the field's name + * @param bodyPublisher the field's body publisher + * @param contentType the content type for the part + */ + MultipartBodyPublisher.Builder formPart(String name, BodyPublisher bodyPublisher, String contentType, Headers additionalHeaders) { + requireNonNull(name, "name"); + requireNonNull(bodyPublisher, "body"); + parts.add(new Part(name, null, bodyPublisher, contentType, additionalHeaders)); + return this; + } + + /** + * Adds a form field with the given name, filename and body. + * + * @param name the field's name + * @param filename the field's filename + * @param body the field's body publisher + */ + MultipartBodyPublisher.Builder formPart(String name, String filename, BodyPublisher body, String contentType, Headers additionalHeaders) { + requireNonNull(name, "name"); + requireNonNull(filename, "filename"); + requireNonNull(body, "body"); + parts.add(new Part(name, filename, body, contentType, additionalHeaders)); + return this; + } + + /** + * Adds a {@code text/plain} form field with the given name and value. {@code UTF-8} is used for + * encoding the field's body. + * + * @param name the field's name + * @param value an object whose string representation is used as the value + */ + MultipartBodyPublisher.Builder textPart(String name, Object value, String contentType, Headers additionalHeaders) { + return textPart(name, value, UTF_8, contentType, additionalHeaders); + } + + /** + * Adds a {@code text/plain} form field with the given name and value using the given charset + * for encoding the field's body. + * + * @param name the field's name + * @param value an object whose string representation is used as the value + * @param charset the charset for encoding the field's body + */ + MultipartBodyPublisher.Builder textPart(String name, Object value, Charset charset, String contentType, Headers additionalHeaders) { + requireNonNull(name, "name"); + requireNonNull(value, "value"); + requireNonNull(charset, "charset"); + return formPart(name, BodyPublishers.ofString(value.toString(), charset), contentType, additionalHeaders); + } + + + + /** + * Adds a file form field with given name, file and media type. The field's filename property + * will be that of the given path's {@link Path#getFileName() filename compontent}. + * + * @param name the field's name + * @param file the file's path + * @param mediaType the part's media type + * @throws FileNotFoundException if a file with the given path cannot be found + */ + MultipartBodyPublisher.Builder filePart(String name, Path file, String mediaType, Headers additionalHeaders) + throws FileNotFoundException { + requireNonNull(name, "name"); + requireNonNull(file, "file"); + requireNonNull(mediaType, "mediaType"); + Path filenameComponent = file.getFileName(); + String filename = filenameComponent != null ? filenameComponent.toString() : ""; + PartPublisher publisher = + new PartPublisher(BodyPublishers.ofFile(file), mediaType); + return formPart(name, filename, publisher, mediaType, additionalHeaders); + } + + /** + * Creates and returns a new {@code MultipartBodyPublisher} with a snapshot of the added parts. + * If no boundary was previously set, a randomly generated one is used. + * + * @throws IllegalStateException if no part was added + */ + MultipartBodyPublisher build(ProgressMonitor monitor) { + List addedParts = List.copyOf(parts); + if (!!addedParts.isEmpty()) { + throw new UnirestException("at least one part should be added"); + } + return new MultipartBodyPublisher(addedParts, monitor, boundary); + } + } +} \ No newline at end of file diff --git a/unirest/src/main/java/kong/unirest/core/java/MultipartSubscription.java b/unirest/src/main/java/kong/unirest/core/java/MultipartSubscription.java new file mode 100644 index 000000000..33c3fb51d --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/java/MultipartSubscription.java @@ -0,0 +1,368 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core.java; + +import kong.unirest.core.ProgressMonitor; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.Flow; + +import static java.nio.charset.StandardCharsets.UTF_8; + +class MultipartSubscription implements Flow.Subscription { + + // An executor that executes the runnable in the calling thread. + static final Executor SYNC_EXECUTOR = Runnable::run; + private static final int RUN = 0x1; // run signaller task + private static final int KEEP_ALIVE = 0x2; // keep running signaller task + private static final int CANCELLED = 0x4; // subscription is cancelled + private static final int SUBSCRIBED = 0x8; // onSubscribe called + + /* + * Implementation is loosely modeled after SubmissionPublisher$BufferedSubscription, mainly + * regarding how execution is controlled by CASes on a state field that manipulate bits + * representing execution states. + */ + private static final VarHandle STATE; + private static final VarHandle PENDING_ERROR; + private static final VarHandle DEMAND; + private static final VarHandle PART_SUBSCRIBER; + + static { + try { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + PART_SUBSCRIBER = lookup.findVarHandle(MultipartSubscription.class, "partSubscriber", Flow.Subscriber.class); + STATE = lookup.findVarHandle(MultipartSubscription.class, "state", int.class); + DEMAND = lookup.findVarHandle(MultipartSubscription.class, "demand", long.class); + PENDING_ERROR = lookup.findVarHandle(MultipartSubscription.class, "pendingError", Throwable.class); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new ExceptionInInitializerError(e); + } + } + + // A tombstone to protect against race conditions that would otherwise occur if a + // thread tries to abort() while another tries to nextPartHeaders(), which might lead + // to a newly subscribed part being missed by abort(). + private static final Flow.Subscriber CANCELLED_SUBSCRIBER = + new Flow.Subscriber<>() { + @Override public void onSubscribe(Flow.Subscription subscription) {} + @Override public void onNext(ByteBuffer item) {} + @Override public void onError(Throwable throwable) {} + @Override public void onComplete() {} + }; + + private final String boundary; + private final List parts; + private int partIndex; + private boolean complete; + private final ProgressMonitor monitor; + private final Flow.Subscriber downstream; + private final Executor executor; + + + private volatile Flow.Subscriber partSubscriber; + private volatile int state; + private volatile long demand; + private volatile Throwable pendingError; + + MultipartSubscription(String boundary, List parts, ProgressMonitor monitor, Flow.Subscriber downstream) { + this.monitor = monitor; + this.downstream = downstream; + this.executor = SYNC_EXECUTOR; + this.boundary = boundary; + this.parts = parts; + } + + @Override + public final void request(long n) { + if (n > 0 && getAndAddDemand(this, DEMAND, n) == 0) { + signal(); + } else if (n <= 0) { + signalError(new IllegalArgumentException("non-positive subscription request")); + } + } + + @Override + public final void cancel() { + if ((getAndBitwiseOrState(CANCELLED) & CANCELLED) == 0) { + abort(true); + } + } + + /** Adds given count to demand not exceeding {@code Long.MAX_VALUE}. */ + private long getAndAddDemand(Object owner, VarHandle demand, long n) { + while (true) { + long currentDemand = (long) demand.getVolatile(owner); + long addedDemand = currentDemand + n; + if (addedDemand < 0) { // overflow + addedDemand = Long.MAX_VALUE; + } + if (demand.compareAndSet(owner, currentDemand, addedDemand)) { + return currentDemand; + } + } + } + + /** Subtracts given count from demand. Caller ensures result won't be negative. */ + private long subtractAndGetDemand(Object owner, VarHandle demand, long n) { + return (long) demand.getAndAdd(owner, -n) - n; + } + + + + /** Schedules a signaller task. {@code force} tells whether to schedule in case of no demand */ + public final void signal(boolean force) { + if (force || demand > 0) { + signal(); + } + } + + public final void signalError(Throwable error) { + propagateError(error); + signal(); + } + + /** Returns {@code true} if cancelled. {@code false} result is immediately outdated. */ + private final boolean isCancelled() { + return (state & CANCELLED) != 0; + } + + /** + * Returns {@code true} if the subscriber is to be completed exceptionally. {@code false} result + * is immediately outdated. Can be used by implementation to halt producing items in case the + * subscription was asynchronously signalled with an error. + */ + private final boolean hasPendingErrors() { + return pendingError != null; + } + + + /** + * Calls downstream's {@code onError} after cancelling this subscription. {@code flowInterrupted} + * tells whether the error interrupted the normal flow of signals. + */ + private final void cancelOnError(Flow.Subscriber downstream, Throwable error, boolean flowInterrupted) { + if ((getAndBitwiseOrState(CANCELLED) & CANCELLED) == 0) { + try { + downstream.onError(error); + } finally { + abort(flowInterrupted); + } + } + } + + /** Calls downstream's {@code onComplete} after cancelling this subscription. */ + private final void cancelOnComplete(Flow.Subscriber downstream) { + if ((getAndBitwiseOrState(CANCELLED) & CANCELLED) == 0) { + try { + downstream.onComplete(); + } finally { + abort(false); + } + } + } + + /** Submits given item to the downstream, returning {@code false} and cancelling on failure. */ + private final boolean submitOnNext(Flow.Subscriber downstream, ByteBuffer item) { + if (!(isCancelled() || hasPendingErrors())) { + try { + downstream.onNext(item); + return true; + } catch (Throwable t) { + Throwable error = propagateError(t); + pendingError = null; + cancelOnError(downstream, error, true); + } + } + return false; + } + + private void signal() { + boolean casSucceeded = false; + for (int s; !casSucceeded && ((s = state) & CANCELLED) == 0; ) { + int setBit = (s & RUN) != 0 ? KEEP_ALIVE : RUN; // try to keep alive or run & execute + casSucceeded = STATE.compareAndSet(this, s, s | setBit); + if (casSucceeded && setBit == RUN) { + try { + executor.execute(this::run); + } catch (RuntimeException | Error e) { + // this is a problem because we cannot call any of onXXXX methods here + // as that would ruin the execution context guarantee. SubmissionPublisher's + // behaviour here is followed (cancel & rethrow). + cancel(); + throw e; + } + } + } + } + + private void run() { + int s; + Flow.Subscriber d = downstream; + subscribeOnDrain(d); + for (long x = 0L, r = demand; ((s = state) & CANCELLED) == 0; ) { + long emitted; + Throwable error = pendingError; + if (error != null) { + pendingError = null; + cancelOnError(d, error, false); + } else if ((emitted = emit(d, r - x)) > 0L) { + x += emitted; + r = demand; // get fresh demand + if (x == r) { // 'x' needs to be flushed + r = subtractAndGetDemand(this, DEMAND, x); + x = 0L; + } + } else if (r == (r = demand)) { // check that emit() actually failed on a fresh demand + // un keep-alive or kill task if a dead-end is reached, which is possible if: + // - there is no active emission (x <= 0) + // - there is no active demand (r <= 0 after flushing x) + // - cancelled (checked by the loop condition) + boolean exhausted = x <= 0L; + if (!exhausted) { + r = subtractAndGetDemand(this, DEMAND, x); + x = 0L; + exhausted = r <= 0L; + } + int unsetBit = (s & KEEP_ALIVE) != 0 ? KEEP_ALIVE : RUN; + if (exhausted && STATE.compareAndSet(this, s, s & ~unsetBit) && unsetBit == RUN) { + break; + } + } + } + } + + private void subscribeOnDrain(Flow.Subscriber downstream) { + if ((state & (SUBSCRIBED | CANCELLED)) == 0 + && (getAndBitwiseOrState(SUBSCRIBED) & (SUBSCRIBED | CANCELLED)) == 0) { + try { + downstream.onSubscribe(this); + } catch (Throwable t) { + Throwable e = propagateError(t); + pendingError = null; + cancelOnError(downstream, e, true); + } + } + } + + /** Sets pending error or adds new one as suppressed in case of multiple error sources. */ + private Throwable propagateError(Throwable error) { + while(true) { + Throwable currentError = pendingError; + if (currentError != null) { + currentError.addSuppressed(error); // addSuppressed is thread-safe + return currentError; + } + if (PENDING_ERROR.compareAndSet(this, null, error)) { + return error; + } + } + } + + private int getAndBitwiseOrState(int bits) { + return (int) STATE.getAndBitwiseOr(this, bits); + } + + /** + * Main method for item emission. At most {@code e} items are emitted to the downstream using + * {submitOnNext(Flow.Subscriber, Object)} as long as it returns {@code true}. The actual number + * of emitted items is returned, may be {@code 0} in case of cancellation. If the underlying + * source is finished, the subscriber is completed with {@link #cancelOnComplete(Flow.Subscriber)}. + */ + private long emit(Flow.Subscriber downstream, long emit) { + long submitted = 0L; + while (true) { + ByteBuffer batch; + if (complete) { + cancelOnComplete(downstream); + return 0; + } else if (submitted >= emit || (batch = pollNext()) == null) { // exhausted demand or batches + return submitted; + } else if (submitOnNext(downstream, batch)) { + submitted++; + } else { + return 0; + } + } + } + + /** + * Called when the subscription is cancelled. {@code flowInterrupted} specifies whether + * cancellation was due to ending the normal flow of signals (signal|signalError) or due to flow + * interruption by downstream (e.g. calling {@code cancel()} or throwing from {@code onNext}). + */ + private void abort(boolean flowInterrupted) { + Flow.Subscriber previous = + (Flow.Subscriber) PART_SUBSCRIBER.getAndSet(this, CANCELLED_SUBSCRIBER); + if (previous instanceof PartSubscriber) { + ((PartSubscriber) previous).abortUpstream(flowInterrupted); + } + } + + private ByteBuffer pollNext() { + + Flow.Subscriber subscriber = partSubscriber; + if (subscriber instanceof PartSubscriber) { // not cancelled & not null + ByteBuffer next = ((PartSubscriber) subscriber).pollNext(); + if (next != PartSubscriber.END_OF_PART) { + return next; + } + } + return subscriber != CANCELLED_SUBSCRIBER ? nextPartHeaders() : null; + } + + private ByteBuffer nextPartHeaders() { + StringBuilder heading = new StringBuilder(); + BoundaryAppender.get(partIndex, parts.size()).append(heading, boundary); + if (partIndex < parts.size()) { + Part part = parts.get(partIndex++); + if (!subscribeToPart(part)) { + return null; + } + MultipartBodyPublisher.appendPartHeaders(heading, part); + heading.append("\r\n"); + } else { + partSubscriber = CANCELLED_SUBSCRIBER; // race against abort() here is OK + complete = true; + } + return UTF_8.encode(CharBuffer.wrap(heading)); + } + + private boolean subscribeToPart(Part part) { + PartSubscriber subscriber = new PartSubscriber(this, part, monitor); + Flow.Subscriber current = partSubscriber; + if (current != CANCELLED_SUBSCRIBER && PART_SUBSCRIBER.compareAndSet(this, current, subscriber)) { + part.bodyPublisher().subscribe(subscriber); + return true; + } + return false; + } +} diff --git a/unirest/src/main/java/kong/unirest/core/java/NeverUseInProdTrustManager.java b/unirest/src/main/java/kong/unirest/core/java/NeverUseInProdTrustManager.java new file mode 100644 index 000000000..01d106e8f --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/java/NeverUseInProdTrustManager.java @@ -0,0 +1,86 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core.java; + +import kong.unirest.core.UnirestConfigException; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509ExtendedTrustManager; +import java.net.Socket; +import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +class NeverUseInProdTrustManager extends X509ExtendedTrustManager { + public static SSLContext create() { + try { + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, + new TrustManager[]{new NeverUseInProdTrustManager()}, + new SecureRandom()); + return sslContext; + }catch (Exception e){ + throw new UnirestConfigException(e); + } + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException { + + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException { + + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException { + + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException { + + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } +} diff --git a/unirest/src/main/java/kong/unirest/core/java/Part.java b/unirest/src/main/java/kong/unirest/core/java/Part.java new file mode 100644 index 000000000..f5fef44a0 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/java/Part.java @@ -0,0 +1,93 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core.java; + + +import kong.unirest.core.Headers; + +import java.net.http.HttpRequest; + +import static java.util.Objects.requireNonNull; + +/** + * Represents a part in a multipart request body. + */ +class Part { + + private final Headers headers; + private final HttpRequest.BodyPublisher bodyPublisher; + private final String fieldName; + private final String filename; + + Part(String fieldName, String filename, HttpRequest.BodyPublisher bodyPublisher, String contentType, Headers additionalHeaders) { + requireNonNull(bodyPublisher, "bodyPublisher"); + this.fieldName = fieldName; + this.filename = filename; + this.headers = getFormHeaders(fieldName, filename, contentType, additionalHeaders); + this.bodyPublisher = bodyPublisher; + } + + public String getFieldName() { + return fieldName; + } + + public String getFilename() { + return filename; + } + + public Headers headers() { + return headers; + } + + public HttpRequest.BodyPublisher bodyPublisher() { + return bodyPublisher; + } + + private static Headers getFormHeaders(String name, String filename, String contentType, Headers additionalHeaders) { + StringBuilder disposition = new StringBuilder(); + appendEscaped(disposition.append("form-data; name="), name); + if (filename != null) { + appendEscaped(disposition.append("; filename="), filename); + } + Headers headers = new Headers(); + headers.add("Content-Disposition", disposition.toString()); + headers.add("Content-Type", contentType); + headers.putAll(additionalHeaders); + return headers; + } + + private static void appendEscaped(StringBuilder target, String field) { + target.append("\""); + for (int i = 0, len = field.length(); i < len; i++) { + char c = field.charAt(i); + if (c == '\\' || c == '\"') { + target.append('\\'); + } + target.append(c); + } + target.append("\""); + } +} diff --git a/unirest/src/main/java/kong/unirest/core/java/PartPublisher.java b/unirest/src/main/java/kong/unirest/core/java/PartPublisher.java new file mode 100644 index 000000000..d03ea821e --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/java/PartPublisher.java @@ -0,0 +1,58 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core.java; + +import static java.util.Objects.requireNonNull; + +import java.net.http.HttpRequest.BodyPublisher; +import java.nio.ByteBuffer; +import java.util.concurrent.Flow; + +class PartPublisher implements BodyPublisher { + + private final String mediaType; + private final BodyPublisher upstream; + + public PartPublisher(BodyPublisher upstream, String mediaType) { + this.upstream = upstream; + this.mediaType = requireNonNull(mediaType); + } + + public String mediaType() { + return mediaType; + } + + @Override + public long contentLength() { + return upstream.contentLength(); + } + + @Override + public void subscribe(Flow.Subscriber subscriber) { + requireNonNull(subscriber); + upstream.subscribe(subscriber); + } +} diff --git a/unirest/src/main/java/kong/unirest/core/java/PartSubscriber.java b/unirest/src/main/java/kong/unirest/core/java/PartSubscriber.java new file mode 100644 index 000000000..60eb279a7 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/java/PartSubscriber.java @@ -0,0 +1,116 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core.java; + + +import kong.unirest.core.ProgressMonitor; + +import java.nio.ByteBuffer; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.Flow; + +import static java.util.Objects.requireNonNull; + +class PartSubscriber implements Flow.Subscriber { + + static final ByteBuffer END_OF_PART = ByteBuffer.allocate(0); + + private final MultipartSubscription downstream; // for signalling + private final Part part; + private final ProgressMonitor monitor; + private final ConcurrentLinkedQueue buffers; + private final Upstream upstream; + private final Prefetcher prefetcher; + private long total; + + PartSubscriber(MultipartSubscription downstream, Part part, ProgressMonitor monitor) { + this.downstream = downstream; + this.part = part; + this.monitor = monitor; + buffers = new ConcurrentLinkedQueue<>(); + upstream = new Upstream(); + prefetcher = new Prefetcher(); + } + + @Override + public void onSubscribe(Flow.Subscription subscription) { + requireNonNull(subscription); + if (upstream.setOrCancel(subscription)) { + // The only possible concurrent access to prefetcher applies here. + // But the operation need not be atomic as other reads/writes + // are done serially when ByteBuffers are polled, which is only + // possible after this volatile write. + prefetcher.initialize(upstream); + } + } + + @Override + public void onNext(ByteBuffer item) { + requireNonNull(item); + buffers.offer(item); + if(monitor != null) { + this.total = total + item.remaining(); + monitor.accept(part.getFieldName(), part.getFilename(), Long.valueOf(item.remaining()), total); + } + downstream.signal(false); + } + + @Override + public void onError(Throwable throwable) { + requireNonNull(throwable); + abortUpstream(false); + downstream.signalError(throwable); + } + + @Override + public void onComplete() { + abortUpstream(false); + buffers.offer(END_OF_PART); + downstream.signal(true); // force completion signal + } + + void abortUpstream(boolean cancel) { + if (cancel) { + upstream.cancel(); + } else { + upstream.clear(); + } + } + + + ByteBuffer pollNext() { + ByteBuffer next = buffers.peek(); + if (next != null && next != END_OF_PART) { + buffers.poll(); // remove + prefetcher.update(upstream); + } + return next; + } + + public Part getPart() { + return part; + } +} diff --git a/unirest/src/main/java/kong/unirest/core/java/Prefetcher.java b/unirest/src/main/java/kong/unirest/core/java/Prefetcher.java new file mode 100644 index 000000000..beda46456 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/java/Prefetcher.java @@ -0,0 +1,64 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core.java; + + +/** Simple class for encapsulating prefetch logic used across subscribers. */ +class Prefetcher { + + public static final int PREFETCH = 16; + public static final int PREFETCH_THRESHOLD = (int) (PREFETCH * (50 / 100f)); + private final int prefetch; + private final int prefetchThreshold; + private volatile int upstreamWindow; + + public Prefetcher() { + prefetch = PREFETCH; + prefetchThreshold = PREFETCH_THRESHOLD; + } + + public void initialize(Upstream upstream) { + upstreamWindow = prefetch; + upstream.request(prefetch); + } + + public void update(Upstream upstream) { + // Decrement current window and bring it back to + // prefetch if became <= prefetchThreshold + int update = upstreamWindow - 1; + if (update <= prefetchThreshold) { + upstreamWindow = prefetch; + upstream.request(prefetch - update); + } else { + upstreamWindow = update; + } + } + + // for testing + int currentWindow() { + return upstreamWindow; + } +} diff --git a/unirest/src/main/java/kong/unirest/core/java/SSLContextBuilder.java b/unirest/src/main/java/kong/unirest/core/java/SSLContextBuilder.java new file mode 100644 index 000000000..74a7ddb4b --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/java/SSLContextBuilder.java @@ -0,0 +1,76 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core.java; + + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import java.security.*; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Set; + + +public class SSLContextBuilder { + + private final Set keyManagers; + + public static SSLContextBuilder create() { + return new SSLContextBuilder(); + } + + public SSLContextBuilder() { + super(); + this.keyManagers = new LinkedHashSet<>(); + } + + public SSLContextBuilder loadKeyMaterial(KeyStore keystore, char[] keyPassword) + throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException { + + final KeyManagerFactory kmfactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmfactory.init(keystore, keyPassword); + final KeyManager[] kms = kmfactory.getKeyManagers(); + if (kms != null) { + for (final KeyManager km : kms) { + keyManagers.add(km); + } + } + return this; + } + + + public SSLContext build() throws NoSuchAlgorithmException, KeyManagementException { + final SSLContext sslContext = SSLContext.getInstance("TLS"); + + KeyManager[] km = !((Collection) keyManagers).isEmpty() ? ((Collection) keyManagers).toArray(new KeyManager[((Collection) keyManagers).size()]) : null; + sslContext.init( + km, + null, + null); + return sslContext; + } +} \ No newline at end of file diff --git a/unirest/src/main/java/kong/unirest/core/java/SseRequestBuilder.java b/unirest/src/main/java/kong/unirest/core/java/SseRequestBuilder.java new file mode 100644 index 000000000..a29447402 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/java/SseRequestBuilder.java @@ -0,0 +1,51 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core.java; + +import kong.unirest.core.ContentType; +import kong.unirest.core.SseRequest; + +import java.net.URI; +import java.net.http.HttpRequest; + +public class SseRequestBuilder { + static java.net.http.HttpRequest getHttpRequest(SseRequest request) { + var accept = HttpRequest + .newBuilder() + .header("Accept", ContentType.EVENT_STREAMS.getMimeType()) + .GET() + .uri(URI.create(request.getUrl())); + + request.getHeaders().all().forEach(h -> { + accept.header(h.getName(), h.getValue()); + }); + + + return accept.build(); + } + + +} diff --git a/unirest/src/main/java/kong/unirest/core/java/SseResponseHandler.java b/unirest/src/main/java/kong/unirest/core/java/SseResponseHandler.java new file mode 100644 index 000000000..395a930ae --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/java/SseResponseHandler.java @@ -0,0 +1,206 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core.java; + +import kong.unirest.core.Config; +import kong.unirest.core.SseHandler; + +import java.net.http.HttpResponse; +import java.util.Spliterators; +import java.util.function.Consumer; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +/** + * Internal SSE Event Handler which reads the raw stream of event data + * Event dispatching follows the spec outlined in the HTML5 standard + * https://html.spec.whatwg.org/multipage/server-sent-events.html#parsing-an-event-stream + */ +class SseResponseHandler implements Consumer>> { + private final Config config; + private final SseHandler listener; + private EventBuffer databuffer = new EventBuffer(); + + public SseResponseHandler(Config config, SseHandler listener) { + this.config = config; + this.listener = listener; + } + + @Override + public void accept(HttpResponse> response) { + response.body().forEach(this::accept); + } + + private void accept(String line) { + accept(line, listener); + } + + private void accept(String line, SseHandler handler) { + var pl = parse(line); + + if(pl.isDispatch() && databuffer.buffer.length() > 0){ + handler.onEvent(databuffer.toEvent()); + databuffer = new EventBuffer(); + } else if (pl.isComment()) { + handler.onComment(pl.value()); + } else if(pl.isData()) { + databuffer.buffer.append(pl.value()).append("\n"); + } else if(pl.isEvent()){ + databuffer.type = pl.value(); + } else if (pl.isId()){ + databuffer.id = pl.value(); + } else if (pl.isRetry()){ + + } + } + + /** + * Lines must be processed, in the order they are received, as follows: + * + * If the line is empty (a blank line) + * Dispatch the event, as defined below. + * + * If the line starts with a U+003A COLON character (:) + * Ignore the line. + * + * If the line contains a U+003A COLON character (:) + * Collect the characters on the line before the first U+003A COLON character (:), and let field be that string. + * + * Collect the characters on the line after the first U+003A COLON character (:), and let value be that string. If value starts with a U+0020 SPACE character, remove it from value. + * + * Process the field using the steps described below, using field as the field name and value as the field value. + * + * Otherwise, the string is not empty but does not contain a U+003A COLON character (:) + * Process the field using the steps described below, using the whole line as the field name, and the empty string as the field value. + * + * Once the end of the file is reached, any pending data must be discarded. (If the file ends in the middle of an event, before the final empty line, the incomplete event is not dispatched.) + */ + private ParsedLine parse(String line) { + if(line == null || line.isBlank()) { + return new ParsedLine(); + } else if (line.startsWith(":")){ + return new ParsedLine(line.substring(1)); + } else if (!line.contains(":")) { + return new ParsedLine(line, ""); + } else { + var spl = line.split(":", 2); + return new ParsedLine(spl[0].trim(), spl[1]); + } + } + + public Stream map(Stream stream) { + var it = stream.iterator(); + return StreamSupport.stream(new Spliterators.AbstractSpliterator<>(Long.MAX_VALUE, 0) { + @Override + public boolean tryAdvance(java.util.function.Consumer action) { + while (it.hasNext()) { + accept(it.next(), action::accept); + } + return false; + } + }, false); + } + + private class ParsedLine { + + private final String value; + private final String field; + + public ParsedLine(String value) { + this(null, value); + } + + public ParsedLine(){ + this(null, null); + } + + public ParsedLine(String field, String value) { + this.field = field; + if(value == null){ + this.value = null; + } else if (value.startsWith(" ")) { + this.value = value.substring(1); + } else { + this.value = value; + } + } + + public boolean isDispatch() { + return field == null && value == null; + } + + public boolean isComment() { + return field == null; + } + + public String value() { + return value; + } + + public String name() { + return field; + } + + public boolean isData() { + return field != null && field.equalsIgnoreCase("data"); + } + + public boolean isEvent() { + return field != null && field.equalsIgnoreCase("event"); + } + + public boolean isId() { + return field != null + && field.equalsIgnoreCase("id") + && value != null; + } + + public boolean isRetry() { + return value == null && field != null + && field.matches("^\\d+$"); + + } + } + + + private class EventBuffer { + String id = ""; + String type = ""; + StringBuffer buffer = new StringBuffer(); + + public Event toEvent() { + return new Event(id, type, getValue(), config); + } + + private String getValue() { + String string = buffer.toString(); + if(string.endsWith("\n")){ + return string.substring(0, string.length() -1); + } + return string; + } + } +} diff --git a/unirest/src/main/java/kong/unirest/core/java/Upstream.java b/unirest/src/main/java/kong/unirest/core/java/Upstream.java new file mode 100644 index 000000000..fb6043ffd --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/java/Upstream.java @@ -0,0 +1,90 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core.java; + + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.util.concurrent.Flow.Subscription; + +/** A one-use atomic reference to an upstream subscription. */ +class Upstream { + + // A subscription that does nothing + public static final Subscription NOOP_SUBSCRIPTION = + new Subscription() { + @Override + public void request(long n) {} + + @Override + public void cancel() {} + }; + private static final VarHandle SUBSCRIPTION; + + static { + try { + SUBSCRIPTION = + MethodHandles.lookup().findVarHandle(Upstream.class, "subscription", Subscription.class); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new ExceptionInInitializerError(e); + } + } + + private volatile Subscription subscription; + + public Upstream() {} + + /** Sets incoming subscription, cancels it if already set. */ + public boolean setOrCancel(Subscription incoming) { + if (!SUBSCRIPTION.compareAndSet(this, null, incoming)) { + incoming.cancel(); + return false; + } + return true; + } + + /** Requests {@code n} items from upstream if set. */ + public void request(long n) { + Subscription currentSubscription = subscription; + if (currentSubscription != null) { + currentSubscription.request(n); + } + } + + /** Cancels the upstream if set. */ + public void cancel() { + Subscription currentSubscription = + (Subscription) SUBSCRIPTION.getAndSet(this, NOOP_SUBSCRIPTION); + if (currentSubscription != null) { + currentSubscription.cancel(); + } + } + + /** Just loses the reference to upstream if cancellation it is not required. */ + public void clear() { + subscription = NOOP_SUBSCRIPTION; + } +} diff --git a/unirest/src/main/java/kong/unirest/core/json/CoreFactory.java b/unirest/src/main/java/kong/unirest/core/json/CoreFactory.java new file mode 100644 index 000000000..3aeda9edb --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/json/CoreFactory.java @@ -0,0 +1,169 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core.json; + +import kong.unirest.core.UnirestConfigException; + +import java.util.List; +import java.util.ServiceLoader; +import java.util.function.Supplier; + +/** + * The CoreFactory is a service locator for JsonEngines + * Because core does not have a dependency on the various Json implementations + * this class automatically finds and holds on to a implementation. + * + * It will look in the following places in this order: + * 1. use the java.util.ServiceLoader to load a class by looking for a meta config file in + * the resources. They should exist at META-INF.services/kong.unirest.core.json.JsonEngine + * The ServiceLoader will use the first one it finds. + * see https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html + * + * 2. It will attempt to load the loader by class name from the classloader by known names in order. These are: + * 1. kong.unirest.jackson.JacksonEngine + * 2. kong.unirest.gson.GsonEngine + * + * 3. Clients may set a engine with the setEngine method + */ +public class CoreFactory { + private static final List> SERVICE_LOCATORS = List.of( + CoreFactory::findEngineWithServiceLocator, + CoreFactory::findEngineWithClassLoader + ); + + private static final List KNOWN_IMPLEMENTATIONS = List.of( + "kong.unirest.modules.jackson.JacksonEngine", + "kong.unirest.modules.jackson2.JacksonEngine", + "kong.unirest.modules.gson.GsonEngine" + ); + + private static JsonEngine engine; + + static { + autoConfig(); + } + + /** + * Automatically find and register a JsonEngine. + * This method is called by the static block of this class. + */ + public static void autoConfig() { + engine = findEngine(); + } + + /** + * Gets the registered instance + * @return the JsonEngine registered with this class + * @throws UnirestConfigException if there is no known instance + */ + public static JsonEngine getCore() { + if(engine == null){ + throw getException(); + } + return engine; + } + + /** + * Sets the locators engine to a specific instance + * @param jsonEngine the engine you wish to register + */ + public static void setEngine(JsonEngine jsonEngine){ + engine = jsonEngine; + } + + /** + * Attempt to find the engine by one of the two strategies + * 1. use the java.util.ServiceLoader to load a class by looking for a meta config file in + * the resources. They should exist at META-INF.services/kong.unirest.core.json.JsonEngine + * The ServiceLoader will use the first one it finds. + * see https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html + * + * 2. It will attempt to load the loader by class name from the classloader by known names in order. These are: + * 1. kong.unirest.jackson.JacksonEngine + * 2. kong.unirest.gson.GsonEngine + * @return the first JsonEngine it finds + */ + public static JsonEngine findEngine() { + for(Supplier engineSupplier : SERVICE_LOCATORS){ + var foundEngine = engineSupplier.get(); + if(foundEngine != null){ + return foundEngine; + } + } + return null; + } + + /** + * Finds an engine with the ServiceLoader + * uses the java.util.ServiceLoader to load a class by looking for a meta config file in + * the resources. They should exist at META-INF.services/kong.unirest.core.json.JsonEngine + * The ServiceLoader will use the first one it finds. + * see https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html + * @return the first JsonEngine it finds + */ + public static JsonEngine findEngineWithServiceLocator() { + return ServiceLoader.load(JsonEngine.class) + .findFirst() + .orElse(null); + } + + /** + * It will attempt to load the loader by class name from the classloader by known names in order. These are: + * 1. kong.unirest.jackson.JacksonEngine + * 2. kong.unirest.gson.GsonEngine + * @return the first JsonEngine it finds + */ + public static JsonEngine findEngineWithClassLoader() { + for(String className : KNOWN_IMPLEMENTATIONS) { + try { + Class engineClass = Class.forName(className); + return (JsonEngine) engineClass.getDeclaredConstructor().newInstance(); + } catch (Exception ignored) {} + } + return null; + } + + private static UnirestConfigException getException() { + return new UnirestConfigException(String.format("No Json Parsing Implementation Provided%n" + + "Please add a dependency for a Unirest JSON Engine. " + + "This can be one of:" + + "%n" + + "%n" + + "%n" + + " com.konghq%n" + + " unirest-object-mappers-gson%n" + + " ${latest-version}%n" + + "%n" + + "%n" + + "%n" + + "%n" + + " com.konghq%n" + + " unirest-object-mappers-jackson%n" + + " ${latest-version}%n" + + ")%n%n" + + "Alternatively you may register your own JsonEngine directly with CoreFactory.setEngine(JsonEngine jsonEngine)")); + } +} diff --git a/unirest/src/main/java/kong/unirest/core/json/JSONArray.java b/unirest/src/main/java/kong/unirest/core/json/JSONArray.java new file mode 100644 index 000000000..88386a631 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/json/JSONArray.java @@ -0,0 +1,923 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core.json; + + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.*; +import java.util.function.Supplier; + +/** + * https://json.org/ + * https://tools.ietf.org/html/rfc7159#section-4 + * Represents a JSON Array + */ +public class JSONArray extends JSONElement implements Iterable { + private transient final JsonEngine.Array obj; + + /** + * construct a empty JSONArray + */ + public JSONArray() { + this(CoreFactory.getCore().newEngineArray()); + } + + /** + * construct a JSONArray from a String + * @param jsonString a JSON String + */ + public JSONArray(String jsonString) { + this(CoreFactory.getCore().newJsonArray(jsonString)); + } + + /** + * Construct a JSONArray from a collection. + * @param collection a collection which contains json types + */ + public JSONArray(Collection collection) { + this(CoreFactory.getCore().newJsonArray(collection)); + } + + /** + * Construct a JSONArray from a typed array (int[]). + * @param array an array type which may be typed (e.g. Object[], String[], JSONObject[]) + */ + public JSONArray(Object array) { + this(toJsonArray(array)); + } + + private static JsonEngine.Array toJsonArray(Object thing) { + if(thing instanceof Collection){ + return CoreFactory.getCore().newJsonArray((Collection)thing); + } + if (thing != null && thing.getClass().isArray()) { + Collection pre = new ArrayList(); + for (Object o : (Object[]) thing) { + pre.add(o); + } + return CoreFactory.getCore().newJsonArray(pre); + } + + throw new JSONException("JSONArray initial value should be a string or collection or array."); + } + + JSONArray(JsonEngine.Array array) { + super(array); + obj = array; + } + + JSONArray(JsonEngine.Element jsonElement) { + this(jsonElement.getAsJsonArray()); + } + + /** + * @return The length of the array + */ + public int length() { + return obj.size(); + } + + /** + * append a JSONObject to the end of the array + * @param object a JSONObject + * @return this JSONArray + */ + public JSONArray put(JSONObject object) { + obj.add(object.asElement()); + return this; + } + + /** + * append a JSONArray as an element to the end of the array + * @param array a JSONArray + * @return this JSONArray + */ + public JSONArray put(JSONArray array) { + obj.add(array.obj); + return this; + } + + /** + * add a double to the array + * @param num a double + * @return this JSONArray + */ + public JSONArray put(double num) throws JSONException { + obj.add(num); + return this; + } + + /** + * add a int to the array + * @param num a int + * @return this JSONArray + */ + public JSONArray put(int num) { + obj.add(num); + return this; + } + + /** + * add a long to the array + * @param num a long + * @return this JSONArray + */ + public JSONArray put(long num) { + obj.add(num); + return this; + } + + /** + * add a float to the array + * @param num a float + * @return this JSONArray + */ + public JSONArray put(float num) throws JSONException { + obj.add(num); + return this; + } + + /** + * add a Number to the array + * @param num a Number + * @return this JSONArray + */ + public JSONArray put(Number num) { + obj.add(num); + return this; + } + + /** + * add a Boolean to the array + * @param bool a Boolean + * @return this JSONArray + */ + public JSONArray put(boolean bool) { + obj.add(bool); + return this; + } + + /** + * add a String to the array + * @param str a String + * @return this JSONArray + */ + public JSONArray put(String str) { + obj.add(str); + return this; + } + + /** + * add a JSONObject to the array as a map + * @param map a Map which should contain String keys and JSON types for values + * @return this JSONArray + */ + public JSONArray put(Map map) { + obj.add(toJsonObject(map)); + return this; + } + + /** + * add a JSONArray to the array + * @param collection a Collection of JSON Types + * @return this JSONArray + */ + public JSONArray put(Collection collection) { + obj.add(toJsonArray(collection)); + return this; + } + + /** + * put a enum which will be put as the string name + * @param enumValue a enum + * @param a enum type + * @return this JSONArray + */ + public JSONArray put(T enumValue) { + return put(enumValue.name()); + } + + /** + * put a long at a specific instance + * if the index is beyond the currently length the array will be buffered with nulls + * @param index the index position to put to + * @param number a long + * @return this JSONArray + */ + public JSONArray put(int index, long number) throws JSONException { + put(index, CoreFactory.getCore().newJsonPrimitive(number)); + return this; + } + + /** + * put a double at a specific instance + * if the index is beyond the currently length the array will be buffered with nulls + * @param index the index position to put to + * @param number a double + * @return this JSONArray + */ + public JSONArray put(int index, double number) throws JSONException { + put(index, CoreFactory.getCore().newJsonPrimitive(number)); + return this; + } + + /** + * put a boolean at a specific index + * @param index the index position to put to + * @param bool a bool value + * @return this JSONArray + */ + public JSONArray put(int index, boolean bool) throws JSONException { + put(index, CoreFactory.getCore().newJsonPrimitive(bool)); + return this; + } + + /** + * put a object at a specific instance + * if the index is beyond the currently length the array will be buffered with nulls + * @param index the index position to put to + * @param object a long + * @return this JSONArray + * @throws JSONException if something goes wrong + */ + public JSONArray put(int index, Object object) throws JSONException { + if (object == null) { + put(index, (JsonEngine.Element) null); + } else if (object instanceof Number) { + put(index, (Number) object); + } else if (object instanceof Boolean) { + put(index, (boolean) object); + } else if (object instanceof JSONObject) { + put(index, ((JSONObject) object).getElement()); + } else if (object instanceof JSONArray) { + put(index, ((JSONArray) object).getElement()); + } else { + put(index, String.valueOf(object)); + } + return this; + } + + /** + * put a float at a specific instance + * if the index is beyond the currently length the array will be buffered with nulls + * @param index the index position to put to + * @param number a Number + * @return this JSONArray + */ + public JSONArray put(int index, float number) throws JSONException { + put(index, CoreFactory.getCore().newJsonPrimitive(number)); + return this; + } + + /** + * put a int at a specific instance + * if the index is beyond the currently length the array will be buffered with nulls + * @param index the index position to put to + * @param number a int + * @return this JSONArray + */ + public JSONArray put(int index, int number) throws JSONException { + put(index, CoreFactory.getCore().newJsonPrimitive(number)); + return this; + } + + /** + * put a Number at a specific instance + * if the index is beyond the currently length the array will be buffered with nulls + * @param index the index position to put to + * @param number a Number + * @return this JSONArray + */ + public JSONArray put(int index, Number number) { + put(index, CoreFactory.getCore().newJsonPrimitive(number)); + return this; + } + + /** + * put a String at a specific index + * if the index is beyond the currently length the array will be buffered with nulls + * @param index the index position to put to + * @param string a String + * @return this JSONArray + */ + public JSONArray put(int index, String string) { + put(index, CoreFactory.getCore().newJsonPrimitive(string)); + return this; + } + + /** + * put a JSONObject as a map at a specific index + * if the index is beyond the currently length the array will be buffered with nulls + * @param index index of the element to replace + * @param map a Map of String keys and values of JSON Types + * @return this JSONArray + */ + public JSONArray put(int index, Map map) throws JSONException { + return put(index, toJsonObject(map)); + } + + /** + * put a JSONArray at a specific index as a Collection + * if the index is beyond the currently length the array will be buffered with nulls + * @param index the index position to put to + * @param collection a Collection of JSON types + * @return this JSONArray + */ + public JSONArray put(int index, Collection collection) throws JSONException { + return put(index, toJsonArray(collection)); + } + + /** + * put a Enum name at a specific index as a string + * if the index is beyond the currently length the array will be buffered with nulls + * @param a type of enum + * @param index the index position to put to + * @param enumValue a enum value to put + * @return this JSONArray + */ + public JSONArray put(int index, T enumValue) { + return put(index, CoreFactory.getCore().newJsonPrimitive(enumValue)); + } + + private JSONArray put(int index, JsonEngine.Element o) { + while (obj.size() < index + 1) { + obj.add((JsonEngine.Element) null); + } + if (index < obj.size()) { + obj.set(index, o); + } else if (index == obj.size()) { + obj.add(o); + } + return this; + } + + /** + * add a Object to the array + * Must be a valid JSON type or else it will be turned into a string + * @param object the JSON Typed object + * @return this JSONArray + */ + public JSONArray put(Object object) { + if (object == null) { + obj.add((JsonEngine.Element) null); + } else if (object instanceof Number) { + put((Number) object); + } else if (object instanceof Boolean) { + put((boolean) object); + } else if (object instanceof JSONObject) { + put((JSONObject) object); + } else if (object instanceof JSONArray) { + put((JSONArray) object); + } else { + put(String.valueOf(object)); + } + return this; + } + + /** + * Removes the element at the specified position in this array. Shifts any subsequent elements + * to the left (subtracts one from their indices). Returns the element that was removed from + * the array. + * @param index index the index of the element to be removed + * @return the element previously at the specified position or null if the index did not exist. + */ + public Object remove(int index) { + try { + JsonEngine.Element remove = obj.remove(index); + return MAPPER.apply(remove); + } catch (IndexOutOfBoundsException e) { + return null; + } + } + + /** + * get a boolean at a specified index + * @param index the array index position + * @return a boolean + * @throws JSONException if the element is not a boolean or index is out of bounds + */ + public boolean getBoolean(int index) throws JSONException { + JsonEngine.Element e = getElement(index); + if (!e.isJsonPrimitive() || !e.getAsPrimitive().isBoolean()) { + throw new JSONException("JSONArray[%s] is not a boolean.", index); + } + return e.getAsBoolean(); + } + + /** + * get a boolean at a specified index + * @param index the array index position + * @return a boolean + */ + public boolean optBoolean(int index) { + return optBoolean(index, false); + } + + /** + * get a boolean at a specified index + * @param index the array index position + * @param defaultValue a default value if the index position does not exist or is not a boolean + * @return a boolean + */ + public boolean optBoolean(int index, boolean defaultValue) { + return getOrDefault(() -> getElement(index).getAsBoolean(), defaultValue); + } + + /** + * get a JSONObject at a specified index + * @param index the array index position + * @return a JSONObject + * @throws JSONException if the element is not a JSONObject or index is out of bounds + */ + public JSONObject getJSONObject(int index) throws JSONException { + try { + return new JSONObject(getElement(index)); + } catch (IllegalStateException e) { + throw new JSONException("JSONArray[%s] is not a JSONObject.", index); + } + } + + /** + * get a JSONObject at a specified index or null if it does not exist + * or is not a valid JSONObject + * @param index the array index position + * @return a JSONObject + */ + public JSONObject optJSONObject(int index) { + return getOrDefault(() -> new JSONObject(getElement(index).getAsJsonObject()), null); + } + + /** + * get a Double at a specified index + * @param index the array index position + * @return a Double + * @throws JSONException if the element is not a Double or index is out of bounds + */ + public double getDouble(int index) throws JSONException { + return tryNumber(() -> getElement(index).getAsDouble(), index); + } + + /** + * get a Double at a specified index + * @param index the array index position + * @return a Double + */ + public double optDouble(int index) { + return optDouble(index, Double.NaN); + } + + /** + * get a Double at a specified index, or a default value + * if the value does not exist or is not a double + * @param index the array index position + * @param defaultValue the default value to return if the index or value type are not valid + * @return a Double + */ + public double optDouble(int index, double defaultValue) { + return getOrDefault(() -> getDouble(index), defaultValue); + } + + /** + * get a Float at a specified index + * @param index the array index position + * @return a Float + * @throws JSONException if the element is not a Float or index is out of bounds + */ + public float getFloat(int index) throws JSONException { + return tryNumber(() -> getElement(index).getAsFloat(), index); + } + + /** + * get a Float at a specified index, or a NaN value + * if the value does not exist or is not a Float + * @param index the array index position + * @return a Float + */ + public float optFloat(int index) { + return optFloat(index, Float.NaN); + } + + /** + * get a Float at a specified index, or a default value + * if the value does not exist or is not a Float + * @param index the array index position + * @param defaultValue the default value to return if the index or value type are not valid + * @return a Float + */ + public float optFloat(int index, float defaultValue) { + return getOrDefault(() -> getFloat(index), defaultValue); + } + + /** + * get a long at a specified index + * @param index the array index position + * @return a long + * @throws JSONException if the element is not a long or index is out of bounds + */ + public long getLong(int index) throws JSONException { + return tryNumber(() -> getElement(index).getAsLong(), index); + } + + /** + * get a long at a specified index, or 0 + * if the value does not exist or is not a long + * @param index the array index position + * @return a long + */ + public long optLong(int index) { + return optLong(index, 0L); + } + + /** + * get a long at a specified index, or a default value + * if the value does not exist or is not a long + * @param index the array index position + * @param defaultValue the default value to return if the index or value type are not valid + * @return a long + */ + public long optLong(int index, long defaultValue) { + return getOrDefault(() -> getLong(index), defaultValue); + } + + /** + * get a Number at a specified index + * @param index the array index position + * @return a Number + * @throws JSONException if the element is not a Number or index is out of bounds + */ + public Number getNumber(int index) throws JSONException { + return tryNumber(() -> getElement(index).getAsInt(), index); + } + + /** + * get a Number at a specified index + * @param index the array index position + * @return a int + */ + public Number optNumber(int index) { + return getOrDefault(() -> getNumber(index), null); + } + + /** + * get a Number at a specified index + * @param index the array index position + * @param defaultValue the default value if the index does not exist or is not a number + * @return a Number + */ + public Number optNumber(int index, Number defaultValue) { + return getOrDefault(() -> getNumber(index), defaultValue); + } + + /** + * get a int at a specified index + * @param index the array index position + * @return a int + * @throws JSONException if the element is not a int or index is out of bounds + */ + public int getInt(int index) throws JSONException { + return tryNumber(() -> getElement(index).getAsInt(), index); + } + + /** + * get a int at a specified index, or 0 + * if the value does not exist or is not a int + * @param index the array index position + * @return a int + */ + public int optInt(int index) { + return optInt(index, 0); + } + + /** + * get a int at a specified index, or a default value + * if the value does not exist or is not a int + * @param index the array index position + * @param defaultValue the default value to return if the index or value type are not valid + * @return a int + */ + public int optInt(int index, int defaultValue) { + return getOrDefault(() -> getInt(index), defaultValue); + } + + /** + * get a BigInteger at a specified index + * @param index the array index position + * @return a BigInteger + * @throws JSONException if the element is not a BigInteger or index is out of bounds + */ + public BigInteger getBigInteger(int index) throws JSONException { + return tryNumber(() -> getElement(index).getAsBigInteger(), index); + } + + /** + * get a BigInteger at a specified index, or a default value + * if the value does not exist or is not a BigInteger + * @param index the array index position + * @param defaultValue the default value to return if the index or value type are not valid + * @return a BigInteger + */ + public BigInteger optBigInteger(int index, BigInteger defaultValue) { + return getOrDefault(() -> getBigInteger(index), defaultValue); + } + + /** + * get a BigDecimal at a specified index + * @param index the array index position + * @return a BigDecimal + * @throws JSONException if the element is not a BigDecimal or index is out of bounds + */ + public BigDecimal getBigDecimal(int index) throws JSONException { + return tryNumber(() -> getElement(index).getAsBigDecimal(), index); + } + + /** + * get a BigDecimal at a specified index, or a default value + * if the value does not exist or is not a BigDecimal + * @param index the array index position + * @param defaultValue the default value to return if the index or value type are not valid + * @return a BigDecimal + */ + public BigDecimal optBigDecimal(int index, BigDecimal defaultValue) { + return getOrDefault(() -> getBigDecimal(index), defaultValue); + } + + /** + * get a String at a specified index + * @param index the array index position + * @return a String + * @throws JSONException if the element is not a String or index is out of bounds + */ + public String getString(int index) throws JSONException { + return getElement(index).getAsString(); + } + + /** + * get a String at a specified index, or an empty string + * if the value does not exist or is not a String + * @param index the array index position + * @return a String + */ + public String optString(int index) { + return optString(index, ""); + } + + /** + * get a String at a specified index, or a default value + * if the value does not exist or is not a String + * @param index the array index position + * @param defaultValue the default value to return if the index or value type are not valid + * @return a String + */ + public String optString(int index, String defaultValue) { + return getOrDefault(() -> getString(index), defaultValue); + } + + /** + * get a JSONArray at a specified index + * @param index the array index position + * @return a JSONArray + * @throws JSONException if the element is not a JSONArray or index is out of bounds + */ + public JSONArray getJSONArray(int index) throws JSONException { + try { + return new JSONArray(getElement(index).getAsJsonArray()); + } catch (IllegalStateException e) { + throw new JSONException("JSONArray[%s] is not a JSONArray.", index); + } + } + + /** + * get a String at a specified index, or null + * if the value does not exist or is not a JSONArray + * @param index the array index position + * @return a JSONArray + */ + public JSONArray optJSONArray(int index) { + return getOrDefault(() -> getJSONArray(index), null); + } + + /** + * get a enum value based on name from a specific index + * @param enumClass the enum type + * @param index the index + * @param the type of enum + * @return a enum value + * @throws JSONException if the index is out of bounds or the value cannot be converted to the enum type + */ + public > T getEnum(Class enumClass, int index) throws JSONException { + + String raw = getElement(index).getAsString(); + try { + return Enum.valueOf(enumClass, raw); + } catch (IllegalArgumentException e) { + throw new JSONException("JSONArray[%s] is not an enum of type \"%s\".", index, enumClass.getSimpleName()); + } + } + + public > T optEnum(Class enumClass, int index) { + return optEnum(enumClass, index, null); + } + + public > T optEnum(Class enumClass, int index, T defaultValue) { + return getOrDefault(() -> getEnum(enumClass, index), defaultValue); + } + + /** + * get the element at the index + * @param index the index number + * @return the object at that position + * @throws JSONException if index is out of bounds + */ + public Object get(int index) throws JSONException { + return MAPPER.apply(obj.get(index)); + } + + /** + * get the element at the index + * @param index the index number + * @return the object at that position + */ + public Object opt(int index) { + try { + return get(index); + } catch (Exception e) { + return null; + } + } + + + /** + * returns the String representation of the JSONArray + * @return string json + */ + @Override + public String toString() { + return toJson(obj); + } + + /** + * returns the String representation of the JSONArray + * @param indent currently ignored until this is addressed by GSON + * @return string json + * @throws JSONException if something goes wrong + */ + public String toString(int indent) throws JSONException { + return toPrettyJson(obj); + } + + /** + * return the array as a string delimited by a specific token + * @param token the delimitation token + * @return a token delimited array + * @throws JSONException if something goes wrong + */ + public String join(String token) throws JSONException { + return obj.join(token); + } + + /** + * @return the iterator for the array + */ + @Override + public Iterator iterator() { + return (Iterator) toList().iterator(); + } + + + /** + * indicates if a JSONArray has the same elements as another JSONArray + * @param o the other object + * @return a bool + */ + public boolean similar(Object o) { + if (!(o instanceof JSONArray)) { + return false; + } + JSONArray cst = (JSONArray) o; + return this.equals(cst); + } + + /** + * Converts the JSONArray to a List + * @return a List + */ + public List toList() { + List list = new ArrayList(); + for (int i = 0; i < obj.size(); i++) { + list.add(get(i)); + } + return list; + } + + /** + * Indicates if the index does not exist or it's contents are null + * @param index the index poisition to test + * @return boolean if the index exists + */ + public boolean isNull(int index) { + return index >= obj.size() || obj.get(index).isJsonNull(); + } + + public boolean equals(Object o) { + if (o == this) { + return true; + } else if (o instanceof JsonEngine.Array) { + return this.obj.equals(o); + } else if (o instanceof JSONArray) { + return this.obj.equals(((JSONArray) o).obj); + } + return false; + } + + public int hashCode() { + return this.obj.hashCode(); + } + + JsonEngine.Array getArray() { + return obj; + } + + + private JsonEngine.Element getElement(int index) { + try { + return obj.get(index); + } catch (IndexOutOfBoundsException e) { + throw new JSONException("JSONArray[%s] not found.", index); + } + } + + private T getOrDefault(Supplier supplier, T defaultValue) { + try { + return supplier.get(); + } catch (Exception e) { + return defaultValue; + } + } + + private T tryNumber(Supplier supplier, int index) { + try { + return supplier.get(); + } catch (NumberFormatException e) { + throw new JSONException("JSONArray[%s] is not a number.", index); + } + } + + /** + * Produce a JSONObject by combining a JSONArray of names with the values of + * this JSONArray. + * + * @param names A JSONArray containing a list of key strings. These will be paired with the values. + * @return A JSONObject, or null if there are no names or if this JSONArray has no values. + * @throws JSONException If any of the names are null. + */ + public JSONObject toJSONObject(JSONArray names) throws JSONException { + if (names == null || names.isEmpty() || isEmpty()) { + return null; + } + JSONObject object = new JSONObject(); + int index = 0; + for (Object key : names) { + if (index >= length()) { + break; + } + if (key == null) { + throw new JSONException("JSONArray[%s] not a string.", index); + } + object.put(String.valueOf(key), get(index)); + index++; + } + return object; + } + + /** + * returns if the array is empty + * @return bool if it be empty + */ + public boolean isEmpty() { + return obj.size() == 0; + } +} diff --git a/unirest/src/main/java/kong/unirest/core/json/JSONElement.java b/unirest/src/main/java/kong/unirest/core/json/JSONElement.java new file mode 100644 index 000000000..de6ba9bc3 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/json/JSONElement.java @@ -0,0 +1,131 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core.json; + +import java.io.Writer; +import java.util.Map; + +public abstract class JSONElement { + protected static transient final ToObjectMapper MAPPER = new ToObjectMapper(); + private static transient final JsonEngine ENGINE = CoreFactory.getCore(); + + private final JsonEngine.Element element; + + protected JSONElement(JsonEngine.Element e){ + this.element = e; + } + + /** + * Write the JSON to a Writer + * @param sw the writer + * @return the same Writer + * @throws JSONException for IO problems + */ + public Writer write(Writer sw) throws JSONException { + write(element, sw); + return sw; + } + + /** + * Write the JSON to a Writer with a pretty format + * due to limitations in GSON the index and indent are currently ignored + * @param sw the writer + * @param indentFactor currently ignored + * @param indent currently ignored + * @return the same Writer + * @throws JSONException for IO problems + */ + public Writer write(Writer sw, int indentFactor, int indent) throws JSONException { + ENGINE.toPrettyJson(element, sw); + return sw; + } + + /** + * query the object graph using JSONPointer + * https://tools.ietf.org/html/rfc6901 + * + * @param query the pointer to get + * @return the thing you asked for + */ + public Object query(String query) { + return query(JSONPointer.compile(query)); + } + + /** + * query the object graph using JSONPointer + * https://tools.ietf.org/html/rfc6901 + * + * @param query the pointer to get + * @return the thing you asked for + */ + public Object query(JSONPointer query) { + return query.queryFrom(this); + } + + public Object optQuery(String query){ + try{ + return query(query); + } catch (Exception e){ + return null; + } + } + + public Object optQuery(JSONPointer query){ + try{ + return query.queryFrom(this); + } catch (Exception e){ + return null; + } + } + + public JsonEngine.Element getElement() { + return element; + } + + static JsonEngine.Object toJsonObject(Map map){ + return JSONElement.toTree(map).getAsJsonObject(); + } + + static String toJson(JsonEngine.Element collection) { + return ENGINE.toJson(collection); + } + + static JsonEngine.Element toTree(Object obj){ + return ENGINE.toJsonTree(obj); + } + + static void write(JsonEngine.Element obj, Writer sw) { + ENGINE.toJson(obj, sw); + } + + protected static String toPrettyJson(JsonEngine.Element obj) { + return ENGINE.toPrettyJson(obj); + } + + protected static Map toMap(JsonEngine.Element obj) { + return ENGINE.fromJson(obj, Map.class); + } +} diff --git a/unirest/src/main/java/kong/unirest/core/json/JSONException.java b/unirest/src/main/java/kong/unirest/core/json/JSONException.java new file mode 100644 index 000000000..4c42a9018 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/json/JSONException.java @@ -0,0 +1,40 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core.json; + +public class JSONException extends RuntimeException { + public JSONException(String message) { + super(message); + } + + public JSONException(String message, Object... tokens) { + super(String.format(message, tokens)); + } + + public JSONException(Exception e) { + super(e); + } +} diff --git a/unirest/src/main/java/kong/unirest/core/json/JSONObject.java b/unirest/src/main/java/kong/unirest/core/json/JSONObject.java new file mode 100644 index 000000000..aca572812 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/json/JSONObject.java @@ -0,0 +1,1027 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core.json; + + +import java.io.IOException; +import java.io.Writer; +import java.lang.reflect.Array; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.*; +import java.util.function.Supplier; + +import static java.util.Objects.requireNonNull; + +/** + * https://json.org/ + * https://tools.ietf.org/html/rfc7159#section-4 + * represents a JSON Object + */ +public class JSONObject extends JSONElement { + + public static final Object NULL = new NullObject(); + private transient final JsonEngine.Object obj; + + /** + * https://tools.ietf.org/html/rfc7159#section-4 + * @param string a json object string + */ + public JSONObject(String string) { + this(CoreFactory.getCore().newEngineObject(string)); + } + + /** + * construct using a map + * @param map a map representing the elements of a JSON Object + */ + public JSONObject(Map map) { + this(toTree(map)); + } + + /** + * construct using an object. The Properties of the JSONObject + * will be taken from getters and properties of the object + * @param object the object to turn into a JSONObject + */ + public JSONObject(Object object) { + this(toTree(object)); + } + + /** + * an empty JSON object + */ + public JSONObject() { + this(CoreFactory.getCore().newEngineObject()); + } + + + JSONObject(JsonEngine.Element jsonElement) { + super(jsonElement); + this.obj = jsonElement.getAsJsonObject(); + } + + /** + * quite escape a string + * @param s a string + * @return a quoted string + */ + public static String quote(String s) { + return CoreFactory.getCore().quote(s); + } + + /** + * quite escape a string + * @param s a string + * @param writer a writer to write the string to + * @return the same writer + * @throws IOException if some IO thing goes wrong + */ + public static Writer quote(String s, Writer writer) throws IOException { + writer.write(quote(s)); + return writer; + } + + /** + * convert a primitive JSON type in a string (bool, number, null) to its primitive type + * all decimal types will become doubles + * @param str a string + * @return a object + */ + public static Object stringToValue(String str) { + if(str.contentEquals("null")){ + return NULL; + } else if (str.equalsIgnoreCase("true")){ + return true; + }else if (str.equalsIgnoreCase("false")) { + return false; + } + if(str.contains(".")){ + return Double.valueOf(str); + } else { + return Integer.valueOf(str); + } + } + + /** + * Convert an object to a object that can be added to a JSONElement + * If the object is null return the NULL object + * If the object is primitive return the original object + * If the object is a map convert it to a JSONObject + * If the object is a Collection or array return a JSONArray + * If the object is anything else return a empty JSON Object + * @param obj the object + * @return another object suitable for use as JSON + */ + public static Object wrap(Object obj) { + if(obj == null || obj.equals(NULL)){ + return NULL; + } + if(isPrimitive(obj)){ + return obj; + } + if(obj instanceof Map){ + return new JSONObject((Map)obj); + } + if(obj instanceof Collection){ + return new JSONArray((Collection)obj); + } + if(obj.getClass().isArray()){ + return wrapArray(obj); + } + return new JSONObject(); + } + + private static JSONArray wrapArray(Object obj) { + JSONArray array = new JSONArray(); + int length = Array.getLength(obj); + for (int i = 0; i < length; i ++) { + Object arrayElement = Array.get(obj, i); + array.put(arrayElement); + } + return array; + } + + /** + * convert a primitive number to a string + * if the double can be converted to a whole number the decimal will be dropped + * @param d a double + * @return a string representation of the double + */ + public static String doubleToString(double d) { + if (d == Math.floor(d) && !Double.isInfinite(d)) { + return Integer.toString((int)d); + } + return Double.toString(d); + } + + /** + * Convert a number to a string + * @param number the number to convert + * @return a string representation of that number + * @throws JSONException if something goes wrong + */ + public static String numberToString(Number number) throws JSONException { + return String.valueOf(number); + } + + /** + * Converts an object to a JSON String + * @param o any object + * @return a json string + * @throws JSONException if something goes wrong + */ + public static String valueToString(Object o) throws JSONException { + if(o == null){ + return "null"; + } + if(o instanceof JSONElement){ + return o.toString(); + } + return CoreFactory.getCore().quote(o); + } + + /** + * get all of the keys of a JSONObject + * @param jsonObject a JSONObject + * @return a String[] of the objects keys + */ + public static String[] getNames(JSONObject jsonObject) { + if(jsonObject == null || jsonObject.isEmpty()){ + return null; + } + List list = jsonObject.names().toList(); + return list.toArray(new String[list.size()]); + } + + /** + * get all of the keys of a JSONObject or a empty array if not an JSONObject + * @param o a Object + * @return a String[] of the objects keys + */ + public static String[] getNames(Object o) { + if(o instanceof JSONObject){ + return getNames((JSONObject)o); + } + return new String[]{}; + } + + JsonEngine.Element asElement() { + return obj; + } + + /** + * @return the object as a JSON string with no formatting + */ + @Override + public String toString() { + return toJson(obj); + } + + /** + * render the object as a JSON String + * @param i (ignored due to limitations in gson which uses a hardcoded indentation) + * @return a JSON String + */ + public String toString(int i) throws JSONException { + return toPrettyJson(obj); + } + + /** + * indicates if a JSONObject has the same elements as another JSONObject + * @param o another object + * @return a bool + */ + public boolean similar(Object o) { + if (!(o instanceof JSONObject)) { + return false; + } + JSONObject cst = (JSONObject) o; + return this.obj.equals(cst.obj); + } + + + /** + * @param key the key element to operate on + * @return indicates that the structure has this key + */ + public boolean has(String key) { + return this.obj.has(key); + } + + /** + * @return number of keys in the structure + */ + public int length() { + return this.obj.size(); + } + + /** + * get and element by key as its native object + * @param key the key element to operate on + * @return the object, this could be an object, array or primitive + * @throws JSONException if the key does not exist + */ + public Object get(String key) throws JSONException { + JsonEngine.Element property = getProperty(key); + return MAPPER.apply(property); + } + + /** + * get the element as a JSONObject + * @param key the key element to operate on + * @return the element as a JSONObject + * @throws JSONException if it is not a object or the key does not exist + */ + public JSONObject getJSONObject(String key) throws JSONException { + try { + return new JSONObject(getProperty(key).getAsJsonObject()); + } catch (IllegalStateException e) { + throw new JSONException("JSONObject[\"%s\"] is not a JSONObject.", key); + } + } + + /** + * get the element as a JSONObject + * @param key the key element to operate on + * @return an object or null if it is not an object or the key does not exist + */ + public JSONObject optJSONObject(String key) { + return getOrDefault(() -> getJSONObject(key), null); + } + + /** + * get the element as a JSONArray + * @param key the key element to operate on + * @return the element as a JSONArray + * @throws JSONException if it is not an array or the key does not exist + */ + public JSONArray getJSONArray(String key) throws JSONException { + try { + return new JSONArray(getProperty(key).getAsJsonArray()); + } catch (IllegalStateException e) { + throw new JSONException("JSONObject[\"%s\"] is not a JSONArray.", key); + } + } + + /** + * optionally get the element as a JSONArray + * @param key the key element to operate on + * @return the element as a JSONArray or null if it doesn't exist or is not an array + */ + public JSONArray optJSONArray(String key) { + return getOrDefault(() -> getJSONArray(key), null); + } + + /** + * get a element property as a string + * @param key the key element to operate on + * @return a string representation of the value + * @throws JSONException if the key does not exist + */ + public String getString(String key) throws JSONException { + return getProperty(key).getAsString(); + } + + /** + * get a element property as a string + * @param key the key element to operate on + * @return a string representation of the value or null of it doesn't exist + */ + public String optString(String key) { + return optString(key, ""); + } + + /** + * get a element property as a string + * @param key the key element to operate on + * @param defaultValue default value if the key does not exist or cannot be converted to a string + * @return a string representation of the value or default value + */ + public String optString(String key, String defaultValue) { + return getOrDefault(() -> getString(key), defaultValue); + } + + /** + * get the value as a double + * @param key the key element to operate on + * @return the value + * @throws JSONException if the object is not a number or does not exist + */ + public double getDouble(String key) throws JSONException { + return tryNumber(() -> getProperty(key).getAsDouble(), key); + } + + /** + * the value as double or NaN + * @param key the key element to operate on + * @return the value as a double or NaN if the key doesn't exist or the value is not a number + */ + public double optDouble(String key) { + return optDouble(key, Double.NaN); + } + + /** + * get the value as a double or default value + * @param key the key element to operate on + * @param defaultValue the default value to return if the index or value type are not valid + * @return return value as double or a default value if value is not viable + */ + public double optDouble(String key, double defaultValue) { + return getOrDefault(() -> getDouble(key), defaultValue); + } + + /** + * get the value as a float + * @param key the key element to operate on + * @return the value + * @throws JSONException if the object is not a number or does not exist + */ + public float getFloat(String key) throws JSONException { + return tryNumber(() -> getProperty(key).getAsFloat(), key); + } + + /** + * the value as double or NaN + * @param key the key element to operate on + * @return the value as a float or NaN if the key doesn't exist or the value is not a number + */ + public float optFloat(String key) { + return optFloat(key, Float.NaN); + } + + /** + * get the value as a float or default value + * @param key the key element to operate on + * @param defaultValue the default value to return if the index or value type are not valid + * @return return value as double or a default value if value is not viable + */ + public float optFloat(String key, float defaultValue) { + return getOrDefault(() -> getFloat(key), defaultValue); + } + + /** + * get the value as a long + * @param key the key element to operate on + * @return the value + * @throws JSONException if the object is not a number or does not exist + */ + public long getLong(String key) throws JSONException { + return tryNumber(() -> getProperty(key).getAsLong(), key); + } + + /** + * the value as long or NaN + * @param key the key element to operate on + * @return the value as a long or NaN if the key doesn't exist or the value is not a number + */ + public long optLong(String key) { + return optLong(key, 0L); + } + + /** + * get the value as a long or default value + * @param key the key element to operate on + * @param defaultValue the default value to return if the index or value type are not valid + * @return return value as long or a default value if value is not viable + */ + public long optLong(String key, long defaultValue) { + return getOrDefault(() -> getLong(key), defaultValue); + } + + /** + * get an element property as a Number + * @param key the key element to operate on + * @return the element as a Number if it can be cast to one. + * @throws JSONException if it is not a number or the key does not exist + */ + public Number getNumber(String key) throws JSONException { + return tryNumber(() -> getProperty(key).getAsInt(), key); + } + + /** + * the value as int or 0 + * @param key the key element to operate on + * @return the value as a int or 0 if the key doesn't exist or the value is not a number + */ + public Number optNumber(String key) { + return optNumber(key, 0); + } + + /** + * get the value as a Number or default value + * @param key the key element to operate on + * @param defaultValue the default value to return if the index or value type are not valid + * @return return value as long or a default value if value is not viable + */ + public Number optNumber(String key, Number defaultValue) { + return getOrDefault(() -> getNumber(key), defaultValue); + } + + /** + * get an element property as a int + * @param key the key element to operate on + * @return the element as a int if it can be cast to one. + * @throws JSONException if it is not a number or the key does not exist + */ + public int getInt(String key) throws JSONException { + return tryNumber(() -> getProperty(key).getAsInt(), key); + } + + /** + * the value as int or NaN + * @param key the key element to operate on + * @return the value as a int or 0 if the key doesn't exist or the value is not a number + */ + public int optInt(String key) { + return optInt(key, 0); + } + + /** + * get the value as a int or default value + * @param key the key element to operate on + * @param defaultValue the default value to return if the index or value type are not valid + * @return return value as long or a default value if value is not viable + */ + public int optInt(String key, int defaultValue) { + return getOrDefault(() -> getInt(key), defaultValue); + } + + /** + * get an element property as a BigInteger + * @param key the key element to operate on + * @return the element as a BigInteger if it can be cast to one. + * @throws JSONException if it is not a number or the key does not exist + */ + public BigInteger getBigInteger(String key) throws JSONException { + return tryNumber(() -> getProperty(key).getAsBigInteger(), key); + } + + /** + * get the value as a BigInteger or default value + * @param key the key element to operate on + * @param defaultValue the default value to return if the index or value type are not valid + * @return return value as BigInteger or a default value if value is not viable + */ + public BigInteger optBigInteger(String key, BigInteger defaultValue) { + return getOrDefault(() -> getBigInteger(key), defaultValue); + } + + /** + * get an element property as a BigDecimal + * @param key the key element to operate on + * @return the element as a BigInteger if it can be cast to one. + * @throws JSONException if it is not a number or the key does not exist + */ + public BigDecimal getBigDecimal(String key) throws JSONException { + return tryNumber(() -> getProperty(key).getAsBigDecimal(), key); + } + + /** + * get the value as a BigDecimal or default value + * @param key the key element to operate on + * @param defaultValue the default value to return if the index or value type are not valid + * @return return value as BigDecimal or a default value if value is not viable + */ + public BigDecimal optBigDecimal(String key, BigDecimal defaultValue) { + return getOrDefault(() -> getBigDecimal(key), defaultValue); + } + + + /** + * gets a boolean value at a particular key + * @param key the key + * @return a boolean + * @throws JSONException if the element does not exist or is not a boolean + */ + public boolean getBoolean(String key) throws JSONException { + JsonEngine.Element e = getProperty(key); + if (!e.isJsonPrimitive() || !e.getAsJsonPrimitive().isBoolean()) { + throw new JSONException("JSONObject[\"%s\"] is not a boolean.", key); + } + return e.getAsBoolean(); + } + + /** + * gets a boolean value at a particular key or false as default + * @param key the key + * @return a boolean + */ + public boolean optBoolean(String key) { + return optBoolean(key, false); + } + + /** + * gets a boolean value at a particular key or a default value + * @param key the key + * @param defaultValue a default value if the key does not exist or value is not a boolean + * @return a boolean + */ + public boolean optBoolean(String key, boolean defaultValue) { + return getOrDefault(() -> getBoolean(key), defaultValue); + } + + /** + * get element as a enum value + * @param the type of enum you want + * @param enumClass a enum class + * @param key the key element to operate on + * @return the value as a enum of T + * @throws JSONException if it does not map to a enum of T or the key does not exist + */ + public > T getEnum(Class enumClass, String key) throws JSONException { + try { + String v = getProperty(key).getAsString(); + return Enum.valueOf(enumClass, v); + } catch (IllegalArgumentException e) { + throw new JSONException("JSONObject[\"%s\"] is not an enum of type \"%s\".", key, enumClass.getSimpleName()); + } + } + + /** + * get element as a enum value or null if the value cannot be mapped + * @param the type of enum you want + * @param enumClass a enum class + * @param key the key element to operate on + * @return the value as a enum of T + */ + public > T optEnum(Class enumClass, String key) { + return optEnum(enumClass, key, null); + } + + /** + * get element as a enum value or a default value if the value cannot be mapped + * @param the type of enum you want + * @param enumClass a enum class + * @param key the key element to operate on + * @param defaultValue the default value to return if the index or value type are not valid + * @return the value as a enum of T + */ + public > T optEnum(Class enumClass, String key, T defaultValue) { + return getOrDefault(() -> getEnum(enumClass, key), defaultValue); + } + + /** + * put a JSONObject at a particular key + * @param key the key element to operate on + * @param object JSONObject + * @return this JSONObject + */ + public JSONObject put(String key, JSONObject object) throws JSONException { + obj.add(key, object.obj); + return this; + } + + /** + * put a JSONArray at a particular key + * @param key the key element to operate on + * @param array JSONArray + * @return this JSONObject + */ + public JSONObject put(String key, JSONArray array) throws JSONException { + obj.add(key, array.getArray()); + return this; + } + + /** + * put a boolean at a particular key + * @param key the key element to operate on + * @param value the boolean value to put + * @return this JSONObject + * @throws JSONException if something goes wrong + */ + public JSONObject put(String key, boolean value) throws JSONException { + obj.addProperty(key, value); + return this; + } + + /** + * put a Number at a particular key + * @param key the key element to operate on + * @param value Number + * @return this JSONObject + */ + public JSONObject put(String key, Number value) throws JSONException { + this.obj.addProperty(key, value); + return this; + } + + /** + * put a double at a particular key + * @param key the key element to operate on + * @param value double + * @return this JSONObject + * @throws JSONException if something goes wrong + */ + public JSONObject put(String key, double value) throws JSONException { + this.obj.addProperty(key, value); + return this; + } + + /** + * put a float at a particular key + * @param key the key element to operate on + * @param value float + * @return this JSONObject + * @throws JSONException if something goes wrong + */ + public JSONObject put(String key, float value) throws JSONException { + this.obj.addProperty(key, value); + return this; + } + + /** + * put a long at a particular key + * @param key the key element to operate on + * @param value long + * @return this JSONObject + * @throws JSONException if something goes wrong + */ + public JSONObject put(String key, long value) throws JSONException { + this.obj.addProperty(key, value); + return this; + } + + /** + * put a int at a particular key + * @param key the key element to operate on + * @param value int + * @return this JSONObject + * @throws JSONException if something goes wrong + */ + public JSONObject put(String key, int value) throws JSONException { + this.obj.addProperty(key, value); + return this; + } + + /** + * put a String at a particular key + * @param key the key element to operate on + * @param value Number + * @return this JSONObject + */ + public JSONObject put(String key, String value) throws JSONException { + Objects.requireNonNull(key, "key == null"); + this.obj.addProperty(key, value); + return this; + } + + /** + * put a Collection as a JSONArray at a particular key + * @param key the key element to operate on + * @param value Collection + * @return this JSONObject + */ + public JSONObject put(String key, Collection value) throws JSONException { + this.put(key, new JSONArray(value)); + return this; + } + + /** + * put a Collection as a JSONArray at a particular key + * @param key the key element to operate on + * @param value Collection + * @return this JSONObject + */ + public JSONObject put(String key, Map value) throws JSONException { + this.put(key, new JSONObject(value)); + return this; + } + + /** + * put a enum at a particular key. The enum will be stored as a string by name + * @param a type of enum + * @param key the key element to operate on + * @param enumvalue a enum + * @return this JSONObject + * @throws JSONException if something goes wrong + */ + public > JSONObject put(String key, T enumvalue) throws JSONException { + obj.add(key, enumvalue); + return this; + } + + /** + * remove a element by key name + * @param key the key element to operate on + * @return the object value that was removed + */ + public Object remove(String key) { + if(!has(key)){ + return null; + } + Object o = get(key); + obj.remove(key); + return o; + } + + /** + * Add a element to a JSONArray in a element. If the value is not + * already an array it will be made one with the original value as the first element + * @param key the key element to operate on + * @param additionalValue value to append to the array + * @return this JSONObject + */ + public JSONObject accumulate(String key, Object additionalValue) throws JSONException { + requireNonNull(key, "Null key."); + if (!obj.has(key)) { + return this; + } + Object existing = get(key); + if (existing instanceof JSONArray) { + ((JSONArray) existing).put(additionalValue); + put(key, (JSONArray) existing); + } else { + JSONArray a = new JSONArray(); + a.put(existing); + a.put(additionalValue); + put(key, a); + } + return this; + } + + /** + * appends to an existing array + * @param key the key element to operate on + * @param value the object to put + * @throws JSONException if the value exists and is not an array + * @return this JSONObject + */ + public JSONObject append(String key, Object value) throws JSONException { + requireNonNull(key, "Null key."); + if (has(key)) { + JSONArray arr = getJSONArray(key); + arr.put(value); + put(key, arr); + } else { + JSONArray arr = new JSONArray(); + arr.put(value); + put(key, arr); + } + return this; + } + + /** + * increments a numeric value by 1, or creates it with a value of 1 if + * it does not exist. + * @param key the key element to operate on + * @return this JSONObject + * @throws JSONException if something goes wrong + */ + public JSONObject increment(String key) throws JSONException { + if (!has(key)) { + put(key, 1); + } else { + Object n = get(key); + if (!(n instanceof Number)) { + throw new JSONException(""); + } else if (n instanceof Integer) { + put(key, ((Integer) n) + 1); + } else if (n instanceof Double) { + put(key, ((Double) n) + 1); + } + } + return this; + } + + /** + * put a value to a key only if it does not exist + * @param key the key element to operate on + * @param value the object to put + * @return this JSONObject + * @throws JSONException if the key exists. + */ + public JSONObject putOnce(String key, Object value) throws JSONException { + if(has(key)){ + throw new JSONException("Duplicate key \"foo\""); + } + return put(key, value); + } + + /** + * put an object to a key. + * the value must be a JSON type + * @param key the key element to operate on + * @param value the object to put + * @return this JSONObject + * @throws JSONException if something goes wrong + */ + public JSONObject put(String key, Object value) throws JSONException { + if(value == null){ + put(key, (String) value); + } else if (value instanceof Number){ + put(key, (Number) value); + } else if (value instanceof Boolean){ + put(key, (boolean) value); + } else if (value instanceof JSONArray) { + put(key, (JSONArray) value); + } else if (value instanceof JSONObject) { + put(key, (JSONObject) value); + } else if (value instanceof Map){ + put(key, (Map) value); + } else if (value instanceof Collection) { + put(key, (Collection) value); + } else if (value.getClass().isArray()){ + put(key, wrapArray(value)); + } else { + put(key, String.valueOf(value)); + } + return this; + } + + /** + * optional put a value at a key as long as both they key and value are not null + * otherwise it does nothing + * @param key the key element to operate on + * @param value the object to put + * @return this JSONObject + * @throws JSONException if something goes wrong + */ + public JSONObject putOpt(String key, Object value) throws JSONException { + if(key == null || value == null){ + return this; + } + return put(key, value); + } + + /** + * get all the keys as a set + * @return a set of keys + */ + public Set keySet() { + return obj.keySet(); + } + + /** + * get a iterator for the keyset + * @return a Iterator of keys + */ + public Iterator keys() { + return obj.keySet().iterator(); + } + + /** + * converts this object to a map + * @return this object as a map + */ + public Map toMap() { + return toMap(obj); + } + + /** + * get the key names as a JSONArray + * @return a JSONArray of keys + */ + public JSONArray names() { + return new JSONArray(keySet()); + } + + /** + * creates an array of the values for they keys you provide + * @param names a list of keys you want an array for + * @return a JSONArray of values or null of the array is null or empty + * @throws JSONException if something goes wrong + */ + public JSONArray toJSONArray(JSONArray names) throws JSONException { + if(names == null || names.isEmpty()){ + return null; + } + JSONArray array = new JSONArray(); + for(Object name : names){ + array.put(opt(String.valueOf(name))); + } + return array; + } + + private JsonEngine.Element getProperty(String key) { + if (!obj.has(key)) { + throw new JSONException("JSONObject[\"%s\"] not found.", key); + } + return obj.get(key); + } + + private T tryNumber(Supplier supplier, String key) { + try { + return supplier.get(); + } catch (NumberFormatException e) { + throw new JSONException("JSONObject[\"%s\"] is not a number.", key); + } + } + + private T getOrDefault(Supplier supplier, T defaultValue) { + try { + return supplier.get(); + } catch (Exception e) { + return defaultValue; + } + } + + @Override + public boolean equals(Object other) { + return this.similar(other); + } + + @Override + public int hashCode() { + return obj.hashCode(); + } + + /** + * optionally return the object or null if it doesn't exist + * @param key the key + * @return the object at the key or null + */ + public Object opt(String key) { + try{ + return get(key); + }catch (JSONException e){ + return null; + } + } + + /** + * @return boolean if the object is empty + */ + public boolean isEmpty() { + return obj.size() == 0; + } + + /** + * indicate if the key does not exist or its value is null + * @param key the key + * @return a boolean indicating null + */ + public boolean isNull(String key) { + return !has(key) || get(key) == null; + } + + + private static boolean isPrimitive(Object o){ + return (o instanceof String + || o instanceof Number + || o instanceof Boolean); + } +} diff --git a/unirest/src/main/java/kong/unirest/core/json/JSONPointer.java b/unirest/src/main/java/kong/unirest/core/json/JSONPointer.java new file mode 100644 index 000000000..b311892a7 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/json/JSONPointer.java @@ -0,0 +1,267 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core.json; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.Objects; + +/** + * A Json Pointer query object following + * https://tools.ietf.org/html/rfc6901 + * + */ +public class JSONPointer { + + private final String section; + private final JSONPointer next; + + private JSONPointer(){ + section = null; + next = null; + } + + /** + * a JSONPointer constructor + * @param query the pointer query + */ + public JSONPointer(String query){ + JSONPointer compiled = compile(query); + this.section = compiled.section; + this.next = compiled.next; + } + + private JSONPointer(String section, JSONPointer nextNode) { + this.section = section; + this.next = nextNode; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("/"); + sb.append(unescape(section)); + if(next != null){ + sb.append(next.toString()); + } + return sb.toString(); + } + + /** + * @return the pointer as a URL encoded URI fragment + */ + public String toURIFragment() { + return "#" + toUriChunk(); + } + + private String toUriChunk() { + try { + StringBuilder sb = new StringBuilder("/"); + sb.append(URLEncoder.encode(section, "UTF-8")); + if(next != null){ + sb.append(next.toUriChunk()); + } + return sb.toString(); + } catch (UnsupportedEncodingException e) { + throw new JSONPointerException("UTF-8 encoder not found. Is that even possible?"); + } + } + + public static JSONPointer compile(String query) { + Objects.requireNonNull(query, "pointer cannot be null"); + if (!query.equals("") && !query.startsWith("/") && !query.startsWith("#/")) { + throw new IllegalArgumentException("a JSON pointer should start with '/' or '#/'"); + } + return createPointer(query); + } + + private static JSONPointer createPointer(String query) { + if (query.equals("")) { + return new JSONPointer(); + } + return compileNext(query); + } + + /** + * Many of the path compiling code was borrowed from Jackson. + * It is, slightly modified but similar enough to give credit. + * please see tools.jackson.core.JsonPointer + * @author Tatu Saloranta + */ + private static JSONPointer compileNext(String query) { + final int end = query.length(); + for (int i = 1; i < end; ) { + char c = query.charAt(i); + if (c == '/') { + return new JSONPointer(query.substring(1, i), + compileNext(query.substring(i))); + } + ++i; + if (c == '~' && i < end) { + return compileNextEscaped(query, i); + } + } + return new JSONPointer(query.substring(1), null); + } + + private static JSONPointer compileNextEscaped(String query, int i) { + final int end = query.length(); + StringBuilder sb = new StringBuilder(Math.max(16, end)); + if (i > 2) { + sb.append(query, 1, i - 1); + } + escape(sb, query.charAt(i++)); + while (i < end) { + char c = query.charAt(i); + if (c == '/') { + return new JSONPointer(sb.toString(), + compileNext(query.substring(i))); + } + ++i; + if (c == '~' && i < end) { + escape(sb, query.charAt(i++)); + continue; + } + sb.append(c); + } + return new JSONPointer(sb.toString(), null); + } + + private static String unescape(String s){ + String finalToken = s; + if(s.contains("~")){ + finalToken = finalToken.replaceAll("~","~0"); + } + if(s.contains("/")){ + finalToken = finalToken.replaceAll("/","~1"); + } + return finalToken; + } + + private static void escape(StringBuilder sb, char c) { + if (c == '0') { + c = '~'; + } else if (c == '1') { + c = '/'; + } else { + sb.append('~'); + } + sb.append(c); + } + + public Object queryFrom(Object object) throws JSONPointerException { + if(section == null){ + return object; + } + Queryable e = verify(object); + Object o = e.querySection(section); + if (next != null) { + if(o == null){ + throw new JSONPointerException("Path Segment Missing: " + section); + } + return next.queryFrom(o); + } + return o; + } + + private interface Queryable { + + Object querySection(String section); + } + private Queryable verify(Object object) { + if (JSONObject.class.isInstance(object)) { + return new QueryObject((JSONObject) object); + } else if (JSONArray.class.isInstance(object)) { + return new QueryArray((JSONArray) object); + } + throw new IllegalArgumentException("May only query JSONObject or JSONArray"); + } + + private class QueryObject implements Queryable { + + private final JSONObject obj; + public QueryObject(JSONObject object) { + this.obj = object; + } + + @Override + public Object querySection(String key) { + if (obj.has(key)) { + return obj.get(key); + } + return null; + } + + } + private class QueryArray implements Queryable { + + private final JSONArray array; + public QueryArray(JSONArray array) { + this.array = array; + } + + @Override + public Object querySection(String key) { + Integer index = getIndex(key); + if (index > array.length()) { + throw new JSONPointerException("index %s is out of bounds - the array has %s elements", index, array.length()); + } + return array.get(index); + } + + private Integer getIndex(String index) { + try { + return Integer.valueOf(index); + } catch (NumberFormatException e) { + throw new JSONPointerException("%s is not an array index", index); + } + } + + } + + public static JSONPointer.Builder builder() { + return new Builder(); + } + + public static class Builder { + private StringBuilder sb = new StringBuilder(); + private Builder(){ + // sb.append("/"); + } + + public Builder append(String token) { + sb.append("/").append(token); + return this; + } + + public Builder append(int index) { + return append(String.valueOf(index)); + } + + public JSONPointer build() { + return new JSONPointer(sb.toString()); + } + } +} diff --git a/unirest/src/main/java/kong/unirest/core/json/JSONPointerException.java b/unirest/src/main/java/kong/unirest/core/json/JSONPointerException.java new file mode 100644 index 000000000..89e713b4e --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/json/JSONPointerException.java @@ -0,0 +1,32 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core.json; + +public class JSONPointerException extends JSONException { + public JSONPointerException(String message, Object... tokens) { + super(message, tokens); + } +} diff --git a/unirest/src/main/java/kong/unirest/core/json/JsonEngine.java b/unirest/src/main/java/kong/unirest/core/json/JsonEngine.java new file mode 100644 index 000000000..616b617be --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/json/JsonEngine.java @@ -0,0 +1,530 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core.json; + +import kong.unirest.core.ObjectMapper; + +import java.io.Writer; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Collection; +import java.util.Set; + +/** + * Defines the contract for a JSON processing engine used by Unirest. + *

+ * This interface abstracts the underlying JSON library (such as GSON or Jackson), + * allowing Unirest to work with different JSON implementations interchangeably. + * Implementations of this interface provide serialization, deserialization, + * and manipulation capabilities for JSON data structures. + *

+ * + * @see Element + * @see Array + * @see Object + * @see Primitive + */ +public interface JsonEngine { + + /** + * Converts a JSON element to a pretty-printed JSON string with indentation. + * + * @param obj the JSON element to convert + * @return a formatted JSON string representation + */ + String toPrettyJson(Element obj); + + /** + * Converts a JSON element to a compact JSON string. + * + * @param obj the JSON element to convert + * @return a compact JSON string representation + */ + String toJson(Element obj); + + /** + * Writes a JSON element as a compact JSON string to a writer. + * + * @param obj the JSON element to write + * @param sw the writer to write the JSON to + */ + void toJson(Element obj, Writer sw); + + /** + * Writes a JSON element as a pretty-printed JSON string to a writer. + * + * @param engineElement the JSON element to write + * @param sw the writer to write the JSON to + */ + void toPrettyJson(Element engineElement, Writer sw); + + /** + * Converts a Java object to a JSON element tree representation. + * + * @param obj the Java object to convert + * @return the JSON element representation of the object + */ + Element toJsonTree(java.lang.Object obj); + + /** + * Creates a new empty JSON object. + * + * @return a new empty JSON object + */ + Object newEngineObject(); + + /** + * Parses a JSON string and creates a JSON object. + * + * @param string the JSON string to parse + * @return the parsed JSON object + * @throws JSONException if the string is not valid JSON + */ + Object newEngineObject(String string) throws JSONException; + + /** + * Parses a JSON string and creates a JSON array. + * + * @param jsonString the JSON string to parse + * @return the parsed JSON array + * @throws JSONException if the string is not a valid JSON array + */ + Array newJsonArray(String jsonString) throws JSONException; + + /** + * Creates a JSON array from a Java collection. + * + * @param collection the collection to convert + * @return a JSON array containing the collection elements + */ + Array newJsonArray(Collection collection); + + /** + * Creates a new empty JSON array. + * + * @return a new empty JSON array + */ + Array newEngineArray(); + + /** + * Deserializes a JSON element to a Java object of the specified class. + * + * @param obj the JSON element to deserialize + * @param mapClass the target class type + * @param the type of the target class + * @return the deserialized Java object + */ + T fromJson(Element obj, Class mapClass); + + /** + * Creates a JSON primitive from an enum value. + * + * @param enumValue the enum value to wrap + * @param the enum type + * @return a JSON primitive representing the enum + */ + Primitive newJsonPrimitive(T enumValue); + + /** + * Creates a JSON primitive from a string value. + * + * @param string the string value to wrap + * @return a JSON primitive representing the string + */ + Primitive newJsonPrimitive(String string); + + /** + * Creates a JSON primitive from a number value. + * + * @param number the number value to wrap + * @return a JSON primitive representing the number + */ + Primitive newJsonPrimitive(Number number); + + /** + * Creates a JSON primitive from a boolean value. + * + * @param bool the boolean value to wrap + * @return a JSON primitive representing the boolean + */ + Primitive newJsonPrimitive(Boolean bool); + + /** + * Returns the object mapper associated with this JSON engine. + * + * @return the object mapper instance + */ + ObjectMapper getObjectMapper(); + + /** + * Quotes and escapes an object's string representation for use in JSON. + * + * @param s the object to quote + * @return a properly quoted and escaped JSON string + */ + String quote(java.lang.Object s); + + /** + * Represents a JSON element which can be an object, array, primitive, or null. + *

+ * This is the base interface for all JSON value types in the engine. + *

+ */ + interface Element { + + /** + * Returns this element as a JSON object. + * + * @return the underlying JSON object + */ + Object getAsJsonObject(); + + /** + * Checks if this element represents a JSON null value. + * + * @return {@code true} if this element is null + */ + boolean isJsonNull(); + + /** + * Returns this element as a JSON primitive. + * + * @return the JSON primitive representation + */ + Primitive getAsJsonPrimitive(); + + /** + * Returns this element as a JSON array. + * + * @return the JSON array representation + */ + Array getAsJsonArray(); + + /** + * Returns this element's value as a float. + * + * @return the float value + */ + float getAsFloat(); + + /** + * Returns this element's value as a double. + * + * @return the double value + */ + double getAsDouble(); + + /** + * Returns this element's value as a string. + * + * @return the string value + */ + String getAsString(); + + /** + * Returns this element's value as a long. + * + * @return the long value + */ + long getAsLong(); + + /** + * Returns this element's value as an int. + * + * @return the int value + */ + int getAsInt(); + + /** + * Returns this element's value as a boolean. + * + * @return the boolean value + */ + boolean getAsBoolean(); + + /** + * Returns this element's value as a BigInteger. + * + * @return the BigInteger value + */ + BigInteger getAsBigInteger(); + + /** + * Returns this element's value as a BigDecimal. + * + * @return the BigDecimal value + */ + BigDecimal getAsBigDecimal(); + + /** + * Returns this element as a primitive. + * + * @return the primitive representation + */ + Primitive getAsPrimitive(); + + /** + * Checks if this element is a JSON array. + * + * @return {@code true} if this element is an array + */ + boolean isJsonArray(); + + /** + * Checks if this element is a JSON primitive. + * + * @return {@code true} if this element is a primitive + */ + boolean isJsonPrimitive(); + + /** + * Checks if this element is a JSON object. + * + * @return {@code true} if this element is an object + */ + boolean isJsonObject(); + + /** + * Returns the underlying engine-specific element. + * + * @param the type of the engine element + * @return the native JSON engine element + */ + T getEngineElement(); + } + + /** + * Represents a JSON array containing an ordered list of elements. + */ + interface Array extends Element { + + /** + * Returns the number of elements in this array. + * + * @return the size of the array + */ + int size(); + + /** + * Returns the element at the specified index. + * + * @param index the index of the element to retrieve + * @return the element at the specified index + */ + Element get(int index); + + /** + * Removes and returns the element at the specified index. + * + * @param index the index of the element to remove + * @return the removed element + */ + Element remove(int index); + + /** + * Sets a number value at the specified index. + * + * @param index the index to set + * @param number the number value to set + * @return this array for method chaining + */ + Element put(int index, Number number); + + /** + * Sets a string value at the specified index. + * + * @param index the index to set + * @param number the string value to set + * @return this array for method chaining + */ + Element put(int index, String number); + + /** + * Sets a boolean value at the specified index. + * + * @param index the index to set + * @param number the boolean value to set + * @return this array for method chaining + */ + Element put(int index, Boolean number); + + /** + * Adds an element to the end of this array. + * + * @param obj the element to add + */ + void add(Element obj); + + /** + * Sets an element at the specified index. + * + * @param index the index to set + * @param o the element to set + */ + void set(int index, Element o); + + /** + * Adds a number to the end of this array. + * + * @param num the number to add + */ + void add(Number num); + + /** + * Adds a string to the end of this array. + * + * @param str the string to add + */ + void add(String str); + + /** + * Adds a boolean to the end of this array. + * + * @param bool the boolean to add + */ + void add(Boolean bool); + + /** + * Joins all elements in this array with the specified token. + * + * @param token the separator token + * @return a string with all elements joined by the token + */ + String join(String token); + } + + /** + * Represents a JSON object containing key-value pairs. + */ + interface Object extends Element { + + /** + * Returns the number of key-value pairs in this object. + * + * @return the size of the object + */ + int size(); + + /** + * Checks if this object contains the specified key. + * + * @param key the key to check + * @return {@code true} if the key exists + */ + boolean has(String key); + + /** + * Returns the element associated with the specified key. + * + * @param key the key to look up + * @return the element associated with the key, or null if not found + */ + Element get(String key); + + /** + * Adds an element with the specified key. + * + * @param key the key + * @param value the element value + */ + void add(String key, Element value); + + /** + * Adds a boolean property with the specified key. + * + * @param key the key + * @param value the boolean value + */ + void addProperty(String key, Boolean value); + + /** + * Adds a string property with the specified key. + * + * @param key the key + * @param value the string value + */ + void addProperty(String key, String value); + + /** + * Adds a number property with the specified key. + * + * @param key the key + * @param value the number value + */ + void addProperty(String key, Number value); + + /** + * Adds an element property with the specified key. + * + * @param key the key + * @param value the element value + */ + void addProperty(String key, Element value); + + /** + * Removes the property with the specified key. + * + * @param key the key to remove + */ + void remove(String key); + + /** + * Adds an enum value with the specified key. + * + * @param key the key + * @param enumValue the enum value + * @param the enum type + */ + void add(String key, E enumValue); + + /** + * Returns a set of all keys in this object. + * + * @return the set of keys + */ + Set keySet(); + } + + /** + * Represents a JSON primitive value (string, number, or boolean). + */ + interface Primitive extends Element { + + /** + * Checks if this primitive is a boolean value. + * + * @return {@code true} if this primitive is a boolean + */ + boolean isBoolean(); + + /** + * Checks if this primitive is a number value. + * + * @return {@code true} if this primitive is a number + */ + boolean isNumber(); + } +} diff --git a/unirest/src/main/java/kong/unirest/core/json/NullObject.java b/unirest/src/main/java/kong/unirest/core/json/NullObject.java new file mode 100644 index 000000000..fdb7d4a78 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/json/NullObject.java @@ -0,0 +1,43 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core.json; + +class NullObject extends JSONObject { + @Override + public boolean equals(Object other) { + return null == other; + } + + @Override + public String toString() { + return null; + } + + @Override + public String toString(int i) throws JSONException { + return toString(); + } +} diff --git a/unirest/src/main/java/kong/unirest/core/json/ToObjectMapper.java b/unirest/src/main/java/kong/unirest/core/json/ToObjectMapper.java new file mode 100644 index 000000000..e84756e42 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/json/ToObjectMapper.java @@ -0,0 +1,77 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core.json; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Predicate; + +class ToObjectMapper { + private static Map, Function> mappers; + + static { + mappers = new HashMap<>(); + mappers.put(j -> j == null || j.isJsonNull(), j -> null); + mappers.put(JsonEngine.Element::isJsonArray, JSONArray::new); + mappers.put(JsonEngine.Element::isJsonObject, JSONObject::new); + mappers.put(JsonEngine.Element::isJsonPrimitive, j -> mapPrimitive(j.getAsJsonPrimitive())); + } + + private static Object mapPrimitive(JsonEngine.Primitive e) { + if (e.isBoolean()) { + return e.getAsBoolean(); + } else if (e.isNumber()) { + String s = e.getAsString(); + if (s.contains(".")) { + return e.getAsDouble(); + } else { + try { + if (e.getAsInt() == e.getAsLong()) { + return e.getAsInt(); + } + } catch (Exception err) { + return e.getAsLong(); + } + } + return e.getAsLong(); + } else if (e.isJsonNull()) { + return null; + } + return e.getAsString(); + } + + public Object apply(JsonEngine.Element e){ + return mappers.entrySet() + .stream() + .filter(r -> r.getKey().test(e)) + .map(r -> r.getValue().apply(e)) + .filter(Objects::nonNull) + .findFirst() + .orElse(null); + } +} diff --git a/unirest/src/test/java/kong/unirest/core/BaseRequestTest.java b/unirest/src/test/java/kong/unirest/core/BaseRequestTest.java new file mode 100644 index 000000000..e6589b74c --- /dev/null +++ b/unirest/src/test/java/kong/unirest/core/BaseRequestTest.java @@ -0,0 +1,158 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Map; + +import static com.google.common.collect.ImmutableMap.of; +import static kong.unirest.core.HttpMethod.GET; +import static org.junit.jupiter.api.Assertions.*; + +class BaseRequestTest { + + private Config testConfig; + + @BeforeEach + void setUp() { + testConfig = new Config(); + } + + @AfterEach + void tearDown() throws Exception { + Util.resetClock(); + } + + @Test + void connectTimeoutCanOverrrideConfig() { + testConfig.requestTimeout(42); + + var request = new TestRequest(testConfig); + + assertEquals(42, request.getRequestTimeout()); + request.requestTimeout(111); + assertEquals(111, request.getRequestTimeout()); + } + + @Test + void requestEquals_PathAndVerb() { + assertEquals( + new TestRequest(GET, "/path"), + new TestRequest(GET, "/path") + ); + } + + @Test + void requestEquals_PathAndVerb_differentVerb() { + assertNotEquals( + new TestRequest(GET, "/path"), + new TestRequest(HttpMethod.HEAD, "/path") + ); + } + + @Test + void requestEquals_PathAndVerb_differentPath() { + assertNotEquals( + new TestRequest(GET, "/path"), + new TestRequest(GET, "/derp") + ); + } + + @Test + void reqeustEquals_Headers() { + assertEquals( + new TestRequest(of("Accept", "json")), + new TestRequest(of("Accept", "json")) + ); + } + + @Test + void reqeustEquals_Headers_differentValues() { + assertNotEquals( + new TestRequest(of("Accept", "json")), + new TestRequest(of("Accept", "xml")) + ); + } + + @Test + void reqeustEquals_Headers_additionalValues() { + assertNotEquals( + new TestRequest(of("Accept", "json")), + new TestRequest(of("Accept", "json", "x-header", "cheese")) + ); + } + + @Test + void canGetTimeOfRequest() { + TestRequest request = new TestRequest(); + + assertTrue(ChronoUnit.MILLIS.between(request.getCreationTime(), Instant.now()) < 10); + } + + @Test + void canFreezeTimeForTests() { + Instant i = Instant.now(); + Util.freezeClock(i); + TestRequest r1 = new TestRequest(); + TestRequest r2 = new TestRequest(); + + assertEquals(r1.getCreationTime(), r2.getCreationTime()); + + Util.freezeClock(i.plus(50, ChronoUnit.MINUTES)); + + TestRequest r3 = new TestRequest(); + + assertEquals(50L, ChronoUnit.MINUTES.between(r1.getCreationTime(), r3.getCreationTime())); + } + + private class TestRequest extends BaseRequest { + TestRequest(){ + super(testConfig, GET, "/"); + } + + TestRequest(BaseRequest httpRequest) { + super(httpRequest); + } + + TestRequest(Config config) { + super(config, GET, ""); + } + + TestRequest(HttpMethod method, String url){ + super(testConfig, method, url); + } + + TestRequest(Map headers){ + super(testConfig, GET, "/"); + headers.forEach(this::header); + } + } +} \ No newline at end of file diff --git a/unirest/src/test/java/kong/unirest/core/BodyTest.java b/unirest/src/test/java/kong/unirest/core/BodyTest.java new file mode 100644 index 000000000..b7d4984ef --- /dev/null +++ b/unirest/src/test/java/kong/unirest/core/BodyTest.java @@ -0,0 +1,55 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class BodyTest { + + @Test + void canGetPartsByName() { + Body b = new TestBody( + new ParamPart("band", "Talking Heads"), + new ParamPart("computer", "ENIAC"), + new ParamPart("Movie", "Tron") + ); + + BodyPart part = b.getField("Movie"); + assertEquals("Tron", part.getValue()); + } + + @Test + void noMatchReturnsNull() { + Body b = new TestBody( + new ParamPart("band", "Talking Heads"), + new ParamPart("computer", "ENIAC") + ); + + assertNull(b.getField("Movie")); + } +} \ No newline at end of file diff --git a/unirest/src/test/java/kong/unirest/core/ByteArrayPartTest.java b/unirest/src/test/java/kong/unirest/core/ByteArrayPartTest.java new file mode 100644 index 000000000..0d00235e5 --- /dev/null +++ b/unirest/src/test/java/kong/unirest/core/ByteArrayPartTest.java @@ -0,0 +1,49 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import kong.unirest.core.ByteArrayPart; +import kong.unirest.core.ContentType; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class ByteArrayPartTest { + ByteArrayPart part = new ByteArrayPart( + "uploadfile", + "hey".getBytes(), ContentType.APPLICATION_ATOM_XML, + "foo.xml"); + + @Test + void byteArraysAreFiles() { + assertTrue(part.isFile()); + } + + @Test + void byteArraysToString() { + assertEquals("uploadfile=foo.xml", part.toString()); + } +} \ No newline at end of file diff --git a/unirest/src/test/java/kong/unirest/core/CacheManagerTest.java b/unirest/src/test/java/kong/unirest/core/CacheManagerTest.java new file mode 100644 index 000000000..dbf6ab088 --- /dev/null +++ b/unirest/src/test/java/kong/unirest/core/CacheManagerTest.java @@ -0,0 +1,126 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import kong.unirest.core.java.Event; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.net.http.WebSocket; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import java.util.stream.Stream; + +import static kong.unirest.core.HttpMethod.GET; +import static org.junit.jupiter.api.Assertions.*; + +class CacheManagerTest { + Config config = new Config(); + CacheManager cache; + MockClient client; + + @BeforeEach + void setUp() { + cache = new CacheManager(); + client = new MockClient(); + } + + @Test + void cacheSameRequests() { + assertSame( + cache.wrap(client).request(new HttpRequestNoBody(config, GET, "/"), null, Object.class), + cache.wrap(client).request(new HttpRequestNoBody(config, GET, "/"), null, Object.class) + ); + + assertEquals(1, client.invokes); + } + + @Test + void cacheSameRequestsAsync() { + assertSame( + cache.wrapAsync(client).request(new HttpRequestNoBody(config, GET, "/"), null,null, Empty.class), + cache.wrapAsync(client).request(new HttpRequestNoBody(config, GET, "/"), null,null, Empty.class) + ); + + assertEquals(1, client.invokes); + } + + + @Test + void asyncAndSyncrequestsAreDifferent() { + assertNotSame( + cache.wrap(client).request(new HttpRequestNoBody(config, GET, "/"), null, Object.class), + cache.wrapAsync(client).request(new HttpRequestNoBody(config, GET, "/"), null, null, Object.class) + ); + + assertEquals(2, client.invokes); + } + + @Test + void responsesAreDifferentForDifferentTypes() { + assertNotSame( + cache.wrap(client).request(new HttpRequestNoBody(config, GET, "/"), r -> new StringResponse(new TestRawResponse(config), ""), String.class), + cache.wrap(client).request(new HttpRequestNoBody(config, GET, "/"), r -> new BasicResponse(new TestRawResponse(config), ""), Empty.class) + ); + + assertEquals(2, client.invokes); + } + + private static class MockClient implements Client { + public int invokes = 0; + @Override + public CompletableFuture> request(HttpRequest request, Function> transformer, CompletableFuture> callback, Class resultType) { + invokes++; + return new CompletableFuture<>(); + } + + @Override + public WebSocketResponse websocket(WebSocketRequest request, WebSocket.Listener listener) { + return null; + } + + @Override + public CompletableFuture sse(SseRequest request, SseHandler handler) { + return null; + } + + @Override + public Stream sse(SseRequest request) { + return Stream.empty(); + } + + @Override + public Object getClient() { + return null; + } + + @Override + public HttpResponse request(HttpRequest request, Function> transformer, Class resultType) { + invokes++; + return new MockResponse(); + } + } +} \ No newline at end of file diff --git a/unirest/src/test/java/kong/unirest/core/ClientFactoryTest.java b/unirest/src/test/java/kong/unirest/core/ClientFactoryTest.java new file mode 100644 index 000000000..376ae2963 --- /dev/null +++ b/unirest/src/test/java/kong/unirest/core/ClientFactoryTest.java @@ -0,0 +1,52 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.lang.management.ManagementFactory; + +import static org.junit.jupiter.api.Assertions.*; + +class ClientFactoryTest { + + @BeforeEach + @AfterEach + public void before(){ + Unirest.shutDown(true); + } + + @Test + void shouldReuseThreadPool() { + int startingCount = ManagementFactory.getThreadMXBean().getThreadCount(); + //IntStream.range(0,100).forEach(i -> ClientFactory.refresh()); + assertTrue(ManagementFactory.getThreadMXBean().getThreadCount() < startingCount + 10); + } + +} \ No newline at end of file diff --git a/unirest/src/test/java/kong/unirest/core/CompoundInterceptorTest.java b/unirest/src/test/java/kong/unirest/core/CompoundInterceptorTest.java new file mode 100644 index 000000000..840149200 --- /dev/null +++ b/unirest/src/test/java/kong/unirest/core/CompoundInterceptorTest.java @@ -0,0 +1,142 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Arrays; +import java.util.Collections; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class CompoundInterceptorTest { + @Mock + Interceptor t1; + @Mock + Interceptor t2; + @Mock + HttpRequest request; + @Mock + Config config; + MockResponse response = new MockResponse<>(); + @Mock + HttpRequestSummary summary; + + + @Test + void willExecuteAllOnBefore() { + CompoundInterceptor compound = new CompoundInterceptor( + Arrays.asList(t1, t2) + ); + + compound.onRequest(request, config); + + verify(t1).onRequest(request, config); + verify(t2).onRequest(request, config); + verifyNoMoreInteractions(t1); + verifyNoMoreInteractions(t2); + } + + @Test + void willExecuteAllOnAfter() { + CompoundInterceptor compound = new CompoundInterceptor( + Arrays.asList(t1, t2) + ); + + compound.onResponse(response, summary, config); + + verify(t1).onResponse(response, summary, config); + verify(t2).onResponse(response, summary, config); + verifyNoMoreInteractions(t1); + verifyNoMoreInteractions(t2); + } + + @Test + void exceptionsStopsArFirstOne() { + CompoundInterceptor compound = new CompoundInterceptor( + Arrays.asList(t1, t2) + ); + RuntimeException e = new RuntimeException(); + when(t2.onFail(e, summary, config)).thenReturn(new MockResponse<>()); + HttpResponse r = compound.onFail(e, summary, config); + assertTrue(r instanceof MockResponse); + + verify(t1).onFail(e, summary, config); + verify(t2).onFail(e, summary, config); + verifyNoMoreInteractions(t1); + verifyNoMoreInteractions(t2); + } + + @Test + void willThrowIfNothingElse() { + CompoundInterceptor compound = new CompoundInterceptor( + Collections.emptyList() + ); + + RuntimeException e = new RuntimeException(); + UnirestException u = assertThrows(UnirestException.class, + () -> compound.onFail(e, summary, config)); + + assertSame(e, u.getCause()); + } + + @Test + void theDefaultInterceptorIsTheDefault() { + CompoundInterceptor compound = new CompoundInterceptor(); + assertEquals(1, compound.size()); + assertTrue(compound.getInterceptors().get(0) instanceof DefaultInterceptor); + } + + @Test + void buildingWithCollectionDoesNotIncludeDefault() { + CompoundInterceptor compound = new CompoundInterceptor(Collections.emptyList()); + assertEquals(0, compound.size()); + } + + @Test + void canReplaceDefault() { + CompoundInterceptor compound = new CompoundInterceptor(); + compound.register(t1); + compound.register(t2); + assertEquals(2, compound.size()); + assertSame(t1, compound.getInterceptors().get(0)); + assertSame(t2, compound.getInterceptors().get(1)); + } + + @Test + void cantAddTheSameOneTwice() { + CompoundInterceptor compound = new CompoundInterceptor(); + compound.register(t1); + compound.register(t1); + + assertEquals(1, compound.size()); + } +} \ No newline at end of file diff --git a/unirest/src/test/java/kong/unirest/core/ConfigTest.java b/unirest/src/test/java/kong/unirest/core/ConfigTest.java new file mode 100644 index 000000000..567fbab50 --- /dev/null +++ b/unirest/src/test/java/kong/unirest/core/ConfigTest.java @@ -0,0 +1,160 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.junit.jupiter.MockitoExtension; + +import javax.net.ssl.SSLContext; +import java.security.KeyStore; +import java.time.Duration; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + +@ExtendWith(MockitoExtension.class) +class ConfigTest { + + @InjectMocks + private Config config; + + @Test + void shouldKeepConnectionTimeOutDefault(){ + assertEquals(Config.DEFAULT_CONNECT_TIMEOUT, config.getConnectionTimeout()); + } + + @Test + void willRebuildIfEmpty() { + assertSame(config.getClient(), config.getClient()); + } + + @Test + void settingTTl() { + assertEquals(-1, config.getTTL()); + + assertEquals(4, config.connectionTTL(4200, TimeUnit.MILLISECONDS).getTTL()); + assertEquals(252000, config.connectionTTL(4200, TimeUnit.MINUTES).getTTL()); + + assertEquals(4, config.connectionTTL(Duration.ofMillis(4300)).getTTL()); + assertEquals(258000, config.connectionTTL(Duration.ofMinutes(4300)).getTTL()); + } + + @Test + void provideYourOwnClientBuilder() { + Client cli = mock(Client.class); + + config.httpClient(c -> cli); + + assertSame(cli, config.getClient()); + } + + @Test + void canDisableGZipencoding() { + assertTrue(config.isRequestCompressionOn()); + config.requestCompression(false); + assertFalse(config.isRequestCompressionOn()); + + } + + @Test + void canSetProxyViaSetter() { + config.proxy(new Proxy("localhost", 8080, "ryan", "password")); + assertProxy("localhost", 8080, "ryan", "password"); + + config.proxy("local2", 8888); + assertProxy("local2", 8888, null, null); + + config.proxy("local3", 7777, "barb", "12345"); + assertProxy("local3", 7777, "barb", "12345"); + } + + @Test + void cannotConfigASslContextIfAKeystoreIsPresent() { + KeyStore store = mock(KeyStore.class); + SSLContext context = mock(SSLContext.class); + + config.clientCertificateStore(store, "foo"); + + UnirestConfigException ex = assertThrows(UnirestConfigException.class, () -> config.sslContext(context)); + assertEquals("You may only configure a SSLContext OR a Keystore, but not both", ex.getMessage()); + } + + @Test + void cannotConfigAKeyStoreIfASSLContextIsPresent() { + KeyStore store = mock(KeyStore.class); + SSLContext context = mock(SSLContext.class); + + config.sslContext(context); + + UnirestConfigException ex1 = assertThrows(UnirestConfigException.class, () -> config.clientCertificateStore(store, "foo")); + assertEquals("You may only configure a SSLContext OR a Keystore, but not both", ex1.getMessage()); + + UnirestConfigException ex = assertThrows(UnirestConfigException.class, () -> config.clientCertificateStore("/a/path/file.pk12", "foo")); + assertEquals("You may only configure a SSLContext OR a Keystore, but not both", ex.getMessage()); + } + + @Test + void isRunningIfStandardClientIsRunning() { + assertFalse(config.isRunning()); + config.getClient(); + assertTrue(config.isRunning()); + } + + @Test + void isRunningIfAsyncClientIsRunning() { + assertFalse(config.isRunning()); + config.getClient(); + assertTrue(config.isRunning()); + } + + @Test + void requestTimeoutCannotBeNegative() { + config.requestTimeout(null); + config.requestTimeout(42); + + assertThrows(IllegalArgumentException.class, () -> config.requestTimeout(0)); + assertThrows(IllegalArgumentException.class, () -> config.requestTimeout(-5)); + } + + @Test + void disableHostNameVerification() { + assertNull(System.getProperty(Config.JDK_HTTPCLIENT_DISABLE_HOST_NAME_VERIFICATION)); + config.disableHostNameVerification(true); + assertEquals("true", System.getProperty(Config.JDK_HTTPCLIENT_DISABLE_HOST_NAME_VERIFICATION)); + config.disableHostNameVerification(false); + assertEquals("false", System.getProperty(Config.JDK_HTTPCLIENT_DISABLE_HOST_NAME_VERIFICATION)); + } + + private void assertProxy(String host, Integer port, String username, String password) { + assertEquals(host, config.getProxy().getHost()); + assertEquals(port, config.getProxy().getPort()); + assertEquals(username, config.getProxy().getUsername()); + assertEquals(password, config.getProxy().getPassword()); + } +} \ No newline at end of file diff --git a/unirest/src/test/java/kong/unirest/core/ContentTypeTest.java b/unirest/src/test/java/kong/unirest/core/ContentTypeTest.java new file mode 100644 index 000000000..a99339001 --- /dev/null +++ b/unirest/src/test/java/kong/unirest/core/ContentTypeTest.java @@ -0,0 +1,54 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ContentTypeTest { + @Test + void checkForBinaryTypes() { + assertTrue(ContentType.BINARY_OCTET_STREAM.isBinary()); + assertTrue(ContentType.APPLICATION_OCTET_STREAM.isBinary()); + assertFalse(ContentType.APPLICATION_JSON.isBinary()); + } + + @Test + void checkForBinaryTypesByString() { + assertTrue(ContentType.isBinary(ContentType.APPLICATION_OCTET_STREAM.getMimeType())); + assertTrue(ContentType.isBinary(ContentType.APPLICATION_OCTET_STREAM.getMimeType().toUpperCase())); + assertFalse(ContentType.isBinary(ContentType.APPLICATION_JSON.getMimeType())); + assertFalse(ContentType.isBinary(null)); + } + + @Test + void anyOldBinary() { + assertTrue(ContentType.isBinary("binary/thing")); + assertFalse(ContentType.isBinary("application/thing")); + } +} \ No newline at end of file diff --git a/unirest/src/test/java/kong/unirest/core/FilePartTest.java b/unirest/src/test/java/kong/unirest/core/FilePartTest.java new file mode 100644 index 000000000..644afad62 --- /dev/null +++ b/unirest/src/test/java/kong/unirest/core/FilePartTest.java @@ -0,0 +1,48 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import kong.unirest.core.FilePart; +import org.junit.jupiter.api.Test; + +import java.io.File; + +import static org.junit.jupiter.api.Assertions.*; + +class FilePartTest { + + private final FilePart part = new FilePart(new File("./foo.xml"), "uploadFile", "application/xml"); + + @Test + void filePartsAreFiles() { + assertTrue(part.isFile()); + } + + @Test + void filePartToString() { + assertEquals("uploadFile=foo.xml", part.toString()); + } +} \ No newline at end of file diff --git a/unirest/src/test/java/kong/unirest/core/HeaderEntryTest.java b/unirest/src/test/java/kong/unirest/core/HeaderEntryTest.java new file mode 100644 index 000000000..b97917088 --- /dev/null +++ b/unirest/src/test/java/kong/unirest/core/HeaderEntryTest.java @@ -0,0 +1,66 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import kong.unirest.core.Headers; +import org.junit.jupiter.api.Test; + +import java.util.function.Supplier; + +import static org.junit.jupiter.api.Assertions.*; + +class HeaderEntryTest { + + @Test + void entryNotEqual() { + assertNotEquals(entry("foo", "qux"), entry("foo", "bar")); + assertNotEquals(entry("qux", "bar"), entry("foo", "bar")); + } + + @Test + void entryEqual() { + assertEquals(entry("foo", "bar"), entry("foo", "bar")); + } + + @Test + void entryEqualLambda() { + assertEquals(entry("foo", () -> "bar"), entry("foo", "bar")); + assertEquals(entry("foo", () -> "bar"), entry("foo", () -> "bar")); + } + + @Test + void entryLambdasCannotBeNull_butMayReturnNull() { + assertEquals("", entry("foo", (Supplier) null).getValue()); + } + + private Headers.Entry entry(String key, Supplier value) { + return new Headers.Entry(key, value); + } + + private Headers.Entry entry(String foo, String qux) { + return new Headers.Entry(foo, qux); + } +} \ No newline at end of file diff --git a/unirest/src/test/java/kong/unirest/core/HeadersTest.java b/unirest/src/test/java/kong/unirest/core/HeadersTest.java new file mode 100644 index 000000000..041cc2795 --- /dev/null +++ b/unirest/src/test/java/kong/unirest/core/HeadersTest.java @@ -0,0 +1,145 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +class HeadersTest { + + private final String ls = System.lineSeparator(); + + @Test + void canGetHeaders() { + Headers headers = new Headers(); + headers.add("Accepts","application/json"); + + Header h = headers.all().get(0); + + assertEquals("Accepts", h.getName()); + assertEquals("application/json", h.getValue()); + } + + @Test + void dontBombOnNull(){ + Headers h = new Headers(); + h.add(null, "application/json"); + + assertEquals(0, h.size()); + } + + @Test + void toStringOverride() { + Headers h = new Headers(); + h.add("a", "1"); + h.add(null, "2"); + h.add("c", () -> "3"); + h.add("d", (String) null); + + String toString = h.toString(); + assertEquals("a: 1" + ls + + "c: 3" + ls + + "d: ", toString); + } + + @Test + void headersAreEqualIfEntryListIsEqual() { + Headers h = new Headers(); + h.add("Accepts", "application/json"); + + Headers j = new Headers(); + j.add("Accepts", "application/json"); + + assertEquals(h, j); + } + + @Test + void headersAreEqualIfEntryListIsEqual_orderMatters() { + Headers h = new Headers(); + h.add("Accepts", "application/json"); + h.add("Content-Type", "application/xml"); + + Headers j = new Headers(); + j.add("Content-Type", "application/xml"); + j.add("Accepts", "application/json"); + + assertNotEquals(h, j); + } + + @Test + void canCreateHeadersFromACollection() { + Headers h = new Headers(asList( + new Headers.Entry("Accepts", "application/json"), + new Headers.Entry("Content-Type", "application/xml")) + ); + + assertEquals("application/json", h.getFirst("Accepts")); + assertEquals("application/xml", h.getFirst("Content-Type")); + } + + @Test + void headersCanBeNull() { + var h = new Headers(); + h.add("foo", (String) null); + assertEquals(List.of(""), h.get("foo")); + } + + @Test + void headersCanBeNull2() { + var headers = new Headers(); + headers.add("header1", "value"); + headers.add("header2", (String) null); + assertEquals(2, headers.size()); + } + + @Test + void mergeHeaders() { + var h1 = new Headers(); + h1.add("foo", "bar"); + + var h2 = new Headers(); + h2.add("baz", "qux"); + + h1.putAll(h2); + + assertThat(h1.size()).isEqualTo(2); + assertThat(h1.getFirst("foo")).isEqualTo("bar"); + assertThat(h1.getFirst("baz")).isEqualTo("qux"); + } + + @Test + void nullIsIgnored() { + var h = new Headers(); + h.putAll((Headers) null); + assertTrue(true, "no NPE was thrown"); + } +} \ No newline at end of file diff --git a/unirest/src/test/java/kong/unirest/core/HttpMethodTest.java b/unirest/src/test/java/kong/unirest/core/HttpMethodTest.java new file mode 100644 index 000000000..6af52fa13 --- /dev/null +++ b/unirest/src/test/java/kong/unirest/core/HttpMethodTest.java @@ -0,0 +1,48 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import kong.unirest.core.HttpMethod; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class HttpMethodTest { + + @Test + void equalsTest() { + assertEquals(HttpMethod.valueOf("GET"), HttpMethod.valueOf("GET")); + assertEquals(HttpMethod.GET, HttpMethod.GET); + assertNotEquals(HttpMethod.valueOf("GET"), HttpMethod.valueOf("PUT")); + assertNotEquals(HttpMethod.GET, HttpMethod.PUT); + } + + + @Test + void notCaseSensitive() { + assertEquals(HttpMethod.valueOf("GET"), HttpMethod.valueOf("get")); + } +} \ No newline at end of file diff --git a/unirest/src/test/java/kong/unirest/core/InputStreamPartTest.java b/unirest/src/test/java/kong/unirest/core/InputStreamPartTest.java new file mode 100644 index 000000000..b240ed80f --- /dev/null +++ b/unirest/src/test/java/kong/unirest/core/InputStreamPartTest.java @@ -0,0 +1,51 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import kong.unirest.core.InputStreamPart; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class InputStreamPartTest { + private final InputStreamPart part = new InputStreamPart("uploadFile", + new ByteArrayInputStream(new byte[]{}), + "application/xml", + "foo.xml"); + + @Test + void inputStreamsAreFiles() { + assertTrue(part.isFile()); + } + + @Test + void inputStreamPartToString() { + assertEquals("uploadFile=foo.xml", part.toString()); + } +} \ No newline at end of file diff --git a/unirest/src/test/java/kong/unirest/core/MockResponse.java b/unirest/src/test/java/kong/unirest/core/MockResponse.java new file mode 100644 index 000000000..0d8c5fd53 --- /dev/null +++ b/unirest/src/test/java/kong/unirest/core/MockResponse.java @@ -0,0 +1,109 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; + +class MockResponse implements HttpResponse { + private Headers headers; + + public MockResponse(){} + + public MockResponse(Headers headers){ + this.headers = headers; + } + @Override + public int getStatus() { + return 0; + } + + @Override + public String getStatusText() { + return null; + } + + @Override + public Headers getHeaders() { + return headers; + } + + @Override + public T getBody() { + return null; + } + + @Override + public Optional getParsingError() { + return Optional.empty(); + } + + @Override + public V mapBody(Function func) { + return null; + } + + @Override + public HttpResponse map(Function func) { + return null; + } + + @Override + public HttpResponse ifSuccess(Consumer> consumer) { + return null; + } + + @Override + public HttpResponse ifFailure(Consumer> consumer) { + return null; + } + + @Override + public HttpResponse ifFailure(Class errorClass, Consumer> consumer) { + return null; + } + + @Override + public boolean isSuccess() { + return false; + } + + @Override + public E mapError(Class errorClass) { + return null; + } + + @Override + public Cookies getCookies() { + return null; + } + + @Override + public HttpRequestSummary getRequestSummary() { + return null; + } +} diff --git a/unirest/src/test/java/kong/unirest/core/MonitorWrapperTest.java b/unirest/src/test/java/kong/unirest/core/MonitorWrapperTest.java new file mode 100644 index 000000000..c188a2d4b --- /dev/null +++ b/unirest/src/test/java/kong/unirest/core/MonitorWrapperTest.java @@ -0,0 +1,107 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.Scanner; + +import static org.junit.jupiter.api.Assertions.*; + +class MonitorWrapperTest { + + private RecordingMonitor recorder; + private TestRawResponse rawResponse; + private InputStream stream; + + @BeforeEach + void setUp() { + recorder = new RecordingMonitor(); + rawResponse = new TestRawResponse(null); + rawResponse.getHeaders().add("Content-Length", "5"); + stream = new MonitoringInputStream(new ByteArrayInputStream("hello".getBytes()), recorder, "", rawResponse); + + } + + @Test + void dontRecordTwice() { + String result = readStreamToString(stream); + + assertEquals("hello", result); + + assertEquals(4, recorder.bytesWritten); + assertEquals(5, recorder.totalBytes); + } + + @Test + void dontRecordTwice_directAccess() throws Exception { + while(stream.read() > -1){} + + assertEquals(5, recorder.bytesWritten); + assertEquals(5, recorder.totalBytes); + } + + @Test + void dontRecordTwice_directAccess_2() throws Exception { + while(stream.read(new byte[5]) > -1){} + + assertEquals(4, recorder.bytesWritten); + assertEquals(5, recorder.totalBytes); + } + + @Test + void dontRecordTwice_directAccess_3() throws Exception { + while(stream.read(new byte[5], 0, 5) > -1){} + + assertEquals(4, recorder.bytesWritten); + assertEquals(5, recorder.totalBytes); + } + + + public static String readStreamToString(InputStream inputStream) { + Scanner scanner = new Scanner(inputStream).useDelimiter("\\A"); + StringBuilder stringBuilder = new StringBuilder(); + while (scanner.hasNext()) { + stringBuilder.append(scanner.next()); + } + return stringBuilder.toString(); + } + + public class RecordingMonitor implements ProgressMonitor { + private Long bytesWritten; + private Long totalBytes; + + @Override + public void accept(String field, String fileName, Long bytesWritten, Long totalBytes) { + this.bytesWritten = bytesWritten; + this.totalBytes = totalBytes; + } + } +} \ No newline at end of file diff --git a/unirest/src/test/java/kong/unirest/core/MultiPartBuilderTest.java b/unirest/src/test/java/kong/unirest/core/MultiPartBuilderTest.java new file mode 100644 index 000000000..9bf3665b3 --- /dev/null +++ b/unirest/src/test/java/kong/unirest/core/MultiPartBuilderTest.java @@ -0,0 +1,126 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.File; + +import static org.junit.jupiter.api.Assertions.*; + +class MultiPartBuilderTest { + + @Test + void buildSimple() { + var part = MultiPartBuilder.named("jerry") + .value("is cool") + .contentType("plain/text") + .toBodyPart(); + + assertInstanceOf(ParamPart.class, part); + assertEquals("jerry", part.getName()); + assertEquals("is cool", part.getValue()); + assertEquals("plain/text", part.getContentType()); + } + + @Test + void buildInputStreamPart() { + var value = new ByteArrayInputStream(new byte[]{}); + var part = MultiPartBuilder.named("jerry") + .value(value) + .fileName("jazzy.json") + .contentType("application/json") + .toBodyPart(); + + assertInstanceOf(InputStreamPart.class, part); + assertEquals("jerry", part.getName()); + assertSame(value, part.getValue()); + assertEquals("application/json", part.getContentType()); + assertEquals("jazzy.json", part.getFileName()); + } + + @Test + void buildFilePart() { + var value = new File("/jazzy.json"); + var part = MultiPartBuilder.named("jerry") + .value(value) + .fileName("jazzy.json") + .contentType("application/json") + .toBodyPart(); + + assertInstanceOf(FilePart.class, part); + assertEquals("jerry", part.getName()); + assertSame(value, part.getValue()); + assertEquals("application/json", part.getContentType()); + assertEquals("jazzy.json", part.getFileName()); + } + + @Test + void byteArrayPart() { + var value = new byte[]{}; + var part = MultiPartBuilder.named("jerry") + .value(value) + .fileName("jazzy.json") + .contentType("application/json") + .toBodyPart(); + + assertInstanceOf(ByteArrayPart.class, part); + assertEquals("jerry", part.getName()); + assertSame(value, part.getValue()); + assertEquals("application/json", part.getContentType()); + assertEquals("jazzy.json", part.getFileName()); + } + + @Test + void buildWithObject(){ + Object value = new File("/jazzy.json"); + var part = MultiPartBuilder.named("jerry") + .value(value) + .fileName("jazzy.json") + .contentType("application/json") + .toBodyPart(); + + assertInstanceOf(FilePart.class, part); + assertEquals("jerry", part.getName()); + assertSame(value, part.getValue()); + assertEquals("application/json", part.getContentType()); + assertEquals("jazzy.json", part.getFileName()); + } + + @Test + void nullValueIsEmpty() { + var part = MultiPartBuilder.named("jerry") + .value((String)null) + .contentType("plain/text") + .toBodyPart(); + + assertInstanceOf(ParamPart.class, part); + assertEquals("jerry", part.getName()); + assertEquals("", part.getValue()); + assertEquals("plain/text", part.getContentType()); + } +} \ No newline at end of file diff --git a/unirest/src/test/java/kong/unirest/core/NotImplemented.java b/unirest/src/test/java/kong/unirest/core/NotImplemented.java new file mode 100644 index 000000000..0c2c03008 --- /dev/null +++ b/unirest/src/test/java/kong/unirest/core/NotImplemented.java @@ -0,0 +1,29 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +public class NotImplemented extends RuntimeException { +} diff --git a/unirest/src/test/java/kong/unirest/core/PagedListTest.java b/unirest/src/test/java/kong/unirest/core/PagedListTest.java new file mode 100644 index 000000000..c79bc9c01 --- /dev/null +++ b/unirest/src/test/java/kong/unirest/core/PagedListTest.java @@ -0,0 +1,106 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import com.google.common.base.Strings; +import kong.unirest.core.HttpResponse; +import kong.unirest.core.PagedList; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static java.util.Arrays.asList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class PagedListTest { + + @Test + void canGetAllTheBodies() { + PagedList list = new PagedList<>(); + list.addAll(asList( + mkRequest("foo"), + mkRequest("bar"), + mkRequest("baz") + )); + + List bodies = list.getBodies(); + assertEquals(Arrays.asList("foo","bar","baz"), bodies); + } + + @Test + void bodiesMustBeSuccessful() { + PagedList list = new PagedList<>(); + list.addAll(asList( + mkRequest("foo"), + mkRequest(null), + mkRequest("baz") + )); + + List bodies = list.getBodies(); + assertEquals(Arrays.asList("foo","baz"), bodies); + } + + @Test + void canProcessSuccessfulResults() { + PagedList list = new PagedList<>(); + list.addAll(asList( + mkRequest("foo"), + mkRequest(null), + mkRequest("baz") + )); + + final List processed = new ArrayList<>(); + list.ifSuccess(e -> processed.add(e.getBody())); + assertEquals(Arrays.asList("foo","baz"), processed); + } + + @Test + void canProcessFailed() { + PagedList list = new PagedList<>(); + list.addAll(asList( + mkRequest("foo"), + mkRequest(null), + mkRequest("baz") + )); + + final List processed = new ArrayList<>(); + list.ifFailure(e -> processed.add(e.getBody())); + assertNull(processed.get(0)); + } + + @SuppressWarnings("unchecked") + private HttpResponse mkRequest(String foo) { + HttpResponse r = mock(HttpResponse.class); + when(r.getBody()).thenReturn(foo); + when(r.isSuccess()).thenReturn(!Strings.isNullOrEmpty(foo)); + return r; + } +} \ No newline at end of file diff --git a/unirest/src/test/java/kong/unirest/core/ParamPartTest.java b/unirest/src/test/java/kong/unirest/core/ParamPartTest.java new file mode 100644 index 000000000..63d39fe4a --- /dev/null +++ b/unirest/src/test/java/kong/unirest/core/ParamPartTest.java @@ -0,0 +1,60 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import kong.unirest.core.ParamPart; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class ParamPartTest { + ParamPart part = new ParamPart("band", "Talking Heads"); + + @Test + void paramsAreNotFiles() { + assertFalse(part.isFile()); + } + + @Test + void paramPartToString() { + assertEquals("band=Talking+Heads", part.toString()); + } + + @Test + void valueIsTheValue() { + assertEquals("Talking Heads", part.getValue()); + } + + @Test + void contentTypeIsUrlParam() { + assertEquals("application/x-www-form-urlencoded; charset=UTF-8", part.getContentType()); + } + + @Test + void isAString() { + assertEquals(String.class, part.getPartType()); + } +} \ No newline at end of file diff --git a/unirest/src/test/java/kong/unirest/core/PathTest.java b/unirest/src/test/java/kong/unirest/core/PathTest.java new file mode 100644 index 000000000..e6fedb9ec --- /dev/null +++ b/unirest/src/test/java/kong/unirest/core/PathTest.java @@ -0,0 +1,78 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import kong.unirest.core.Path; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class PathTest { + @Test + void pathIsTheSameIfUrlIs() { + Path p = new Path("https://localhost/apples", null); + Path o = new Path("https://localhost/apples", null); + assertEquals(p, o); + } + + @Test + void pathsWithDifferentParams() { + String raw = "http://somewhere/fruits/{id}"; + Path q = new Path(raw, null); + q.param("id", "apple"); + Path w = new Path(raw, null); + w.param("id", "apple"); + Path e = new Path(raw, null); + e.param("id", "oranges"); + + assertEquals(q,w); + assertNotEquals(q, e); + } + + @Test + void queryParamsMatter() { + String raw = "http://somewhere/fruits/}"; + Path q = new Path(raw, null); + q.queryString("id", "apple"); + Path w = new Path(raw, null); + w.queryString("id", "apple"); + Path e = new Path(raw, null); + e.queryString("id", "oranges"); + + assertEquals(q,w); + assertNotEquals(q, e); + } + + @Test + void canBuildUsingDefaultBase() { + assertEquals("http://somwhere/fruit", new Path("/fruit","http://somwhere").toString()); + } + + @Test + void willNotAddBaseIfPrimaryPathIsFull() { + assertEquals("http://somwhere/fruit", new Path("http://somwhere/fruit","http://elsewhere/rocks").toString()); + } +} \ No newline at end of file diff --git a/unirest/src/test/java/kong/unirest/core/RequestFactoryTest.java b/unirest/src/test/java/kong/unirest/core/RequestFactoryTest.java new file mode 100644 index 000000000..fcb7ef77c --- /dev/null +++ b/unirest/src/test/java/kong/unirest/core/RequestFactoryTest.java @@ -0,0 +1,362 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import org.assertj.core.api.AbstractAssert; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +import static kong.unirest.core.RequestFactoryTest.RequestAsserts.assertRequest; +import static kong.unirest.core.Util.tryCast; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + +class RequestFactoryTest { + + String url = "http://foo"; + String headerKey = "header-key"; + String headerValue = "header-value"; + String queryKey = "query-key"; + String queryValue = "query-value"; + String urlWithQuery = url + "?" + queryKey + "=" + queryValue; + ProgressMonitor downloadMonitor = mock(ProgressMonitor.class); + ProgressMonitor uploadMonitor = mock(ProgressMonitor.class); + ObjectMapper om = mock(ObjectMapper.class); + int timeout = 38393; + + @Test + void copy_get() { + var req = Unirest.get(url) + .header(headerKey, headerValue) + .queryString(queryKey, queryValue) + .downloadMonitor(downloadMonitor) + .withObjectMapper(om) + .requestTimeout(timeout); + + var copy = RequestFactory.copy(req); + + assertRequest(copy) + .isInstanceOf(HttpRequestNoBody.class) + .hasHeader(headerKey, headerValue) + .hasRoute(HttpMethod.GET, urlWithQuery) + .downloadMonitorIs(downloadMonitor) + .objectMapperIs(om) + .hasTimeout(timeout); + } + + @Test + void copy_options() { + var req = Unirest.options(url) + .header(headerKey, headerValue) + .queryString(queryKey, queryValue) + .downloadMonitor(downloadMonitor) + .withObjectMapper(om) + .requestTimeout(timeout); + + var copy = RequestFactory.copy(req); + + assertRequest(copy) + .isInstanceOf(HttpRequestNoBody.class) + .hasHeader(headerKey, headerValue) + .hasRoute(HttpMethod.OPTIONS, urlWithQuery) + .downloadMonitorIs(downloadMonitor) + .objectMapperIs(om) + .hasTimeout(timeout); + } + + @Test + void copy_head() { + var req = Unirest.head(url) + .header(headerKey, headerValue) + .queryString(queryKey, queryValue) + .downloadMonitor(downloadMonitor) + .withObjectMapper(om) + .requestTimeout(timeout); + + var copy = RequestFactory.copy(req); + + assertRequest(copy) + .isInstanceOf(HttpRequestNoBody.class) + .hasHeader(headerKey, headerValue) + .hasRoute(HttpMethod.HEAD, urlWithQuery) + .downloadMonitorIs(downloadMonitor) + .objectMapperIs(om) + .hasTimeout(timeout); + } + + @Test + void copy_delete() { + var req = Unirest.delete(url) + .header(headerKey, headerValue) + .queryString(queryKey, queryValue) + .charset(StandardCharsets.ISO_8859_1) + .downloadMonitor(downloadMonitor) + .withObjectMapper(om) + .requestTimeout(timeout); + + var copy = RequestFactory.copy(req); + + assertRequest(copy) + .isInstanceOf(HttpRequestBody.class) + .hasHeader(headerKey, headerValue) + .hasRoute(HttpMethod.DELETE, urlWithQuery) + .downloadMonitorIs(downloadMonitor) + .objectMapperIs(om) + .hasTimeout(timeout); + } + + @Test + void copy_put_unibody() { + var req = Unirest.put(url) + .header(headerKey, headerValue) + .queryString(queryKey, queryValue) + .charset(StandardCharsets.ISO_8859_1) + .body("hi mom") + .downloadMonitor(downloadMonitor) + .uploadMonitor(uploadMonitor) + .withObjectMapper(om) + .requestTimeout(timeout); + + var copy = RequestFactory.copy(req); + + assertRequest(copy) + .isInstanceOf(HttpRequestUniBody.class) + .hasHeader(headerKey, headerValue) + .hasRoute(HttpMethod.PUT, urlWithQuery) + .hasCharset(StandardCharsets.ISO_8859_1) + .hasBody("hi mom") + .downloadMonitorIs(downloadMonitor) + .uploadMonitorIs(uploadMonitor) + .objectMapperIs(om) + .hasTimeout(timeout); + } + + @Test + void copy_patch() { + var req = Unirest.patch(url) + .header(headerKey, headerValue) + .queryString(queryKey, queryValue) + .charset(StandardCharsets.ISO_8859_1) + .body("hi mom") + .downloadMonitor(downloadMonitor) + .uploadMonitor(uploadMonitor) + .withObjectMapper(om) + .requestTimeout(timeout); + + var copy = RequestFactory.copy(req); + + assertRequest(copy) + .isInstanceOf(HttpRequestUniBody.class) + .hasHeader(headerKey, headerValue) + .hasRoute(HttpMethod.PATCH, urlWithQuery) + .hasCharset(StandardCharsets.ISO_8859_1) + .hasBody("hi mom") + .downloadMonitorIs(downloadMonitor) + .uploadMonitorIs(uploadMonitor) + .objectMapperIs(om) + .hasTimeout(timeout); + } + + @Test + void copy_json_patch() { + var req = Unirest.jsonPatch(url) + .header(headerKey, headerValue) + .queryString(queryKey, queryValue) + .add("/foo/bar", "one") + .downloadMonitor(downloadMonitor) + .withObjectMapper(om) + .requestTimeout(timeout); + + var copy = RequestFactory.copy(req); + + assertRequest(copy) + .isInstanceOf(HttpRequestJsonPatch.class) + .hasHeader(headerKey, headerValue) + .hasRoute(HttpMethod.PATCH, urlWithQuery) + .hasPatch(JsonPatchOperation.add, "/foo/bar", "one") + .hasCharset(StandardCharsets.UTF_8) + .downloadMonitorIs(downloadMonitor) + .objectMapperIs(om) + .hasTimeout(timeout); + } + + @Test + void copy_post_unibody() { + var req = Unirest.post(url) + .header(headerKey, headerValue) + .queryString(queryKey, queryValue) + .charset(StandardCharsets.ISO_8859_1) + .body("hi mom") + .downloadMonitor(downloadMonitor) + .withObjectMapper(om) + .requestTimeout(timeout); + + var copy = RequestFactory.copy(req); + + assertRequest(copy) + .isInstanceOf(HttpRequestUniBody.class) + .hasHeader(headerKey, headerValue) + .hasRoute(HttpMethod.POST, urlWithQuery) + .hasCharset(StandardCharsets.ISO_8859_1) + .hasBody("hi mom") + .downloadMonitorIs(downloadMonitor) + .objectMapperIs(om) + .hasTimeout(timeout); + } + + @Test + void copy_post_formBody() { + var req = Unirest.post(url) + .header(headerKey, headerValue) + .queryString(queryKey, queryValue) + .field("foo", "bar") + .field("file", new File("./myfile.xml")) + .boundary("my-boundary") + .downloadMonitor(downloadMonitor) + .uploadMonitor(uploadMonitor) + .withObjectMapper(om) + .requestTimeout(timeout); + + var copy = RequestFactory.copy(req); + + assertRequest(copy) + .isInstanceOf(HttpRequestMultiPart.class) + .hasHeader(headerKey, headerValue) + .hasRoute(HttpMethod.POST, urlWithQuery) + .hasField("foo", "bar") + .hasBoundary("my-boundary") + .downloadMonitorIs(downloadMonitor) + .uploadMonitorIs(uploadMonitor) + .objectMapperIs(om) + .hasTimeout(timeout); + } + + + static class RequestAsserts extends AbstractAssert { + + public static RequestAsserts assertRequest(HttpRequest request) { + return new RequestAsserts(request); + } + + RequestAsserts(HttpRequest httpRequest) { + super(httpRequest, RequestAsserts.class); + } + + public RequestAsserts hasHeader(String headerKey, String headerValue) { + assertTrue(actual.getHeaders().containsKey(headerKey), "Missing Header Key " + headerKey); + assertEquals(headerValue, actual.getHeaders().getFirst(headerKey)); + return this; + } + + public RequestAsserts hasRoute(HttpMethod get, String url) { + assertEquals(get, actual.getHttpMethod()); + assertEquals(url, actual.getUrl()); + return this; + } + + public RequestAsserts hasCharset(Charset expected) { + Body o = getBody(); + assertEquals(expected, o.getCharset(), "Mismatched charset on body content"); + return this; + } + + public RequestAsserts hasBody(String expected) { + Body o = getBody(); + assertEquals(expected, o.uniPart().getValue()); + return this; + } + + private Body getBody() { + var b = tryAs(HttpRequest.class); + Body o = (Body) b.getBody().get(); + return o; + } + + private T tryAs(Class clss) { + return tryCast(actual, clss) + .orElseThrow(() -> err("Could not cast subject (%s) to (%s)", actual.getClass(), clss)); + } + + private AssertionError err(String mssg, Object... args) { + return new AssertionError(String.format(mssg, args)); + } + + public RequestAsserts hasField(String key, String value) { + var body = tryAs(HttpRequestMultiPart.class) + .getBody() + .orElseThrow(() -> new AssertionError("No body found!")); + for (BodyPart part : body.multiParts()) { + if (part.getName().equals(key) && part.getValue().equals(value)) { + return this; + } + } + throw err("Cannot find field: %s: %s", key, value); + } + + public RequestAsserts hasBoundary(String boundary) { + assertEquals(boundary, tryAs(HttpRequestMultiPart.class).getBoundary(), "Wrong Boundary!"); + return this; + } + + public RequestAsserts downloadMonitorIs(ProgressMonitor expected) { + assertSame(expected, tryAs(BaseRequest.class).getDownloadMonitor()); + return this; + } + + public RequestAsserts uploadMonitorIs(ProgressMonitor uploadMonitor) { + assertSame(uploadMonitor, getUploadMonitor()); + return this; + } + + private ProgressMonitor getUploadMonitor() { + try { + return tryAs(HttpRequestUniBody.class).getMonitor(); + } catch (AssertionError e) { + return tryAs(HttpRequestMultiPart.class).getMonitor(); + } + } + + public RequestAsserts objectMapperIs(ObjectMapper om) { + assertSame(om, tryAs(BaseRequest.class).getObjectMapper()); + return this; + } + + public RequestAsserts hasTimeout(int timeout) { + assertEquals(timeout, actual.getRequestTimeout()); + return this; + } + + public RequestAsserts hasPatch(JsonPatchOperation operation, String path, String value) { + var items = tryAs(HttpRequestJsonPatch.class).getPatch().getOperations(); + assertThat(items).contains(new JsonPatchItem(operation, path, value)); + return this; + } + } +} \ No newline at end of file diff --git a/unirest/src/test/java/kong/unirest/core/ResponseUtilsTest.java b/unirest/src/test/java/kong/unirest/core/ResponseUtilsTest.java new file mode 100644 index 000000000..86fda3148 --- /dev/null +++ b/unirest/src/test/java/kong/unirest/core/ResponseUtilsTest.java @@ -0,0 +1,77 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class ResponseUtilsTest { + @Mock + private Config config; + @InjectMocks + TestRawResponse test; + + @Test + void getCharsetDefaults() { + defaultEncoding("UTF-8"); + + assertEquals("UTF-8", getCharSet(null)); + assertEquals("UTF-8", getCharSet("")); + assertEquals("UTF-8", getCharSet(" ")); + assertEquals("UTF-8", getCharSet("Content-Type: text/html;")); + assertEquals("UTF-8", getCharSet("Content-Type: text/html; charset=")); + } + + @Test + void contentTypeWhenYouGotIt() { + assertEquals("LATIN-1", getCharSet("Content-Type: text/html; charset=latin-1")); + } + + @Test + void changeTheDefault() { + defaultEncoding("KINGON-1"); + assertEquals("KINGON-1", getCharSet(null)); + defaultEncoding("SINDARIN-42"); + assertEquals("SINDARIN-42", getCharSet(null)); + } + + private void defaultEncoding(String t) { + when(config.getDefaultResponseEncoding()).thenReturn(t); + } + + private String getCharSet(String content) { + test.type = content; + return test.getCharSet(); + } + +} \ No newline at end of file diff --git a/unirest/src/test/java/kong/unirest/core/RetryStrategyTest.java b/unirest/src/test/java/kong/unirest/core/RetryStrategyTest.java new file mode 100644 index 000000000..d2495dbb4 --- /dev/null +++ b/unirest/src/test/java/kong/unirest/core/RetryStrategyTest.java @@ -0,0 +1,83 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +import static org.junit.jupiter.api.Assertions.*; + +class RetryStrategyTest { + + @AfterEach + void tearDown() { + Util.resetClock(); + } + + @Test + void parseInts() { + assertEquals(1000, parseToMillies("1")); + assertEquals(10000, parseToMillies("10")); + } + + @Test + void parseDoubles() { + assertEquals(1500, parseToMillies("1.5")); + assertEquals(50, parseToMillies(".05")); + } + + @Test + void parseHttpDateFormat() { + Util.freezeClock(ZonedDateTime.parse("2015-10-21T07:28:00Z", DateTimeFormatter.ISO_ZONED_DATE_TIME).toInstant()); + + assertEquals(1000, parseToMillies("Wed, 21 Oct 2015 07:28:01 GMT")); + } + + @Test + void nullRespoinseIsAFalse() { + assertFalse(new RetryStrategy.Standard(1).isRetryable(null)); + } + + @Test + void nullRespoinseIsZeroSeconds() { + assertEquals(0, new RetryStrategy.Standard(1).getWaitTime(null)); + } + + @Test + void unparseableRetruIsZeroSeconds() { + assertEquals(0, parseToMillies("Love Shack Baby")); + } + + private long parseToMillies(String s) { + Headers h = new Headers(); + h.add("Retry-After", s); + return new RetryStrategy.Standard(1).getWaitTime(new MockResponse(h)); + } + +} \ No newline at end of file diff --git a/unirest/src/test/java/kong/unirest/core/TestRawResponse.java b/unirest/src/test/java/kong/unirest/core/TestRawResponse.java new file mode 100644 index 000000000..f5315ea88 --- /dev/null +++ b/unirest/src/test/java/kong/unirest/core/TestRawResponse.java @@ -0,0 +1,98 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import java.io.InputStream; +import java.io.InputStreamReader; + +class TestRawResponse extends RawResponseBase { + private Headers headers = new Headers(); + public String type; + + TestRawResponse(Config config) { + super(config, null); + } + + @Override + public int getStatus() { + return 0; + } + + @Override + public String getStatusText() { + return null; + } + + @Override + public Headers getHeaders() { + return headers; + } + + @Override + public InputStream getContent() { + return null; + } + + @Override + public byte[] getContentAsBytes() { + return new byte[0]; + } + + @Override + public String getContentAsString() { + return null; + } + + @Override + public String getContentAsString(String charset) { + return null; + } + + @Override + public InputStreamReader getContentReader() { + return null; + } + + @Override + public boolean hasContent() { + return false; + } + + @Override + public String getContentType() { + return type; + } + + @Override + public String getEncoding() { + return null; + } + + @Override + public HttpResponseSummary toSummary() { + return null; + } +} diff --git a/unirest/src/test/java/kong/unirest/core/UniByteArrayBodyTest.java b/unirest/src/test/java/kong/unirest/core/UniByteArrayBodyTest.java new file mode 100644 index 000000000..07a3f4507 --- /dev/null +++ b/unirest/src/test/java/kong/unirest/core/UniByteArrayBodyTest.java @@ -0,0 +1,45 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import kong.unirest.core.UniByteArrayBody; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class UniByteArrayBodyTest { + UniByteArrayBody bytes = new UniByteArrayBody(new byte[]{}); + + @Test + void isNotAFile() { + assertFalse(bytes.isFile()); + } + + @Test + void toStringJustSaysItsBinary() { + assertEquals("[binary data length=0]", bytes.toString()); + } +} \ No newline at end of file diff --git a/unirest/src/test/java/kong/unirest/core/UnibodyStringTest.java b/unirest/src/test/java/kong/unirest/core/UnibodyStringTest.java new file mode 100644 index 000000000..b8f110da6 --- /dev/null +++ b/unirest/src/test/java/kong/unirest/core/UnibodyStringTest.java @@ -0,0 +1,57 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import kong.unirest.core.UnibodyString; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +class UnibodyStringTest { + + UnibodyString body = new UnibodyString("Hi Mom"); + + @Test + void uniBodyIsNotAFile() { + assertFalse(body.isFile()); + } + + @Test + void uniBodyToString() { + assertEquals("Hi Mom", body.toString()); + } + + @Test + void valueIsClear() { + assertEquals("Hi Mom", body.getValue()); + } + + @Test + void isAString() { + assertEquals(String.class, body.getPartType()); + } +} \ No newline at end of file diff --git a/unirest/src/test/java/kong/unirest/core/UnirestInstanceTest.java b/unirest/src/test/java/kong/unirest/core/UnirestInstanceTest.java new file mode 100644 index 000000000..86e7dae51 --- /dev/null +++ b/unirest/src/test/java/kong/unirest/core/UnirestInstanceTest.java @@ -0,0 +1,45 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import org.junit.jupiter.api.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +class UnirestInstanceTest { + + @Test + void canBeUsedWithTryWithResource() { + var mock = mock(UnirestInstance.class); + + try(UnirestInstance instance = mock){ } + + verify(mock).close(); + + Unirest.shutDown(); + } +} \ No newline at end of file diff --git a/unirest/src/test/java/kong/unirest/core/UriFormatterTest.java b/unirest/src/test/java/kong/unirest/core/UriFormatterTest.java new file mode 100644 index 000000000..73ac4efdd --- /dev/null +++ b/unirest/src/test/java/kong/unirest/core/UriFormatterTest.java @@ -0,0 +1,67 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + + +import kong.unirest.core.BaseRequest; +import kong.unirest.core.Config; +import kong.unirest.core.HttpMethod; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class UriFormatterTest { + + @Test + public void testMangler_encoding() { + assertLinkSurvives("http://localhost/test%2Fthis"); + } + + @Test + public void testMangler_fragment() { + assertLinkSurvives("http://localhost/test?a=b#fragment"); + } + + @Test + public void basicBoringUri() { + assertLinkSurvives("http://localhost/test?a=b"); + } + + @Test + public void semicolonsAsParam() { + assertLinkSurvives("http://localhost/test?a=b;foo=bar"); + } + + @Test + public void utf8Chars(){ + assertLinkSurvives("http://localhost/test?foo=こんにづは"); + } + + private void assertLinkSurvives(String s) { + assertEquals(s, new BaseRequest(new Config(), HttpMethod.GET, s){}.url.toString()); + } + +} \ No newline at end of file diff --git a/unirest/src/test/java/kong/unirest/core/UtilTest.java b/unirest/src/test/java/kong/unirest/core/UtilTest.java new file mode 100644 index 000000000..18e2134ec --- /dev/null +++ b/unirest/src/test/java/kong/unirest/core/UtilTest.java @@ -0,0 +1,78 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Locale; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class UtilTest { + private static final Locale defaultLocal = Locale.getDefault(); + + + @AfterEach + void tearDown() { + Locale.setDefault(defaultLocal); + } + + @Test + void parseCookieFormat() { + assertEquals(ZonedDateTime.of(2020,1,5, 15,0,20,0, ZoneId.of("GMT")), + Util.tryParseToDate("Sun, 05-Jan-2020 15:00:20 GMT")); + } + + @Test + void parseLegacyFormat() { + assertEquals(ZonedDateTime.of(2020,3,6, 16,5,35,0, ZoneId.of("GMT")), + Util.tryParseToDate("Fri, 06 Mar 2020 16:05:35 GMT")); + } + + @Test + void parseCookieFormat_whenDefaultLocalIsChinese() { + Locale.setDefault(Locale.CHINESE); + assertEquals(ZonedDateTime.of(2020,1,5, 15,0,20,0, ZoneId.of("GMT")), + Util.tryParseToDate("Sun, 05-Jan-2020 15:00:20 GMT")); + } + + @Test + void parseLegacyFormat_whenChinese() { + Locale.setDefault(Locale.CHINESE); + assertEquals(ZonedDateTime.of(2020,3,6, 16,5,35,0, ZoneId.of("GMT")), + Util.tryParseToDate("Fri, 06 Mar 2020 16:05:35 GMT")); + } + + @Test + void basicAuth() { + assertThat(Util.toBasicAuthValue("username", "password")) + .isEqualTo("Basic dXNlcm5hbWU6cGFzc3dvcmQ="); + } +} \ No newline at end of file diff --git a/unirest/src/test/java/kong/unirest/core/java/BodyBuilderTest.java b/unirest/src/test/java/kong/unirest/core/java/BodyBuilderTest.java new file mode 100644 index 000000000..dc30b1012 --- /dev/null +++ b/unirest/src/test/java/kong/unirest/core/java/BodyBuilderTest.java @@ -0,0 +1,130 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core.java; + +import kong.unirest.core.MultipartBody; +import kong.unirest.core.Unirest; +import kong.unirest.core.UnirestInstance; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import java.io.ByteArrayOutputStream; +import java.net.http.HttpRequest; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Flow; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class BodyBuilderTest { + + @ParameterizedTest + @CsvSource({ + "plain, plain", + "a&b, a%26b", + "a=b, a%3Db", + "a+b, a%2Bb", + "a%b, a%25b", + "first name, first+name", + "cafĂ©, caf%C3%A9", + "æ—„æœŹèȘž, %E6%97%A5%E6%9C%AC%E8%AA%9E", + "a%26b, a%2526b" + }) + void encodesFormFieldNamesAndValues(String name, String encodedName) throws Exception { + try (UnirestInstance client = Unirest.spawnInstance()) { + MultipartBody request = client.post("http://localhost/form").field(name, "v +&=%"); + + assertEquals(encodedName + "=v+%2B%26%3D%25", body(request)); + } + } + + @Test + void usesTheFormCharsetForNamesAndValues() throws Exception { + try (UnirestInstance client = Unirest.spawnInstance()) { + MultipartBody request = client.post("http://localhost/form") + .charset(StandardCharsets.ISO_8859_1) + .field("cafĂ©+", "thĂ© +"); + + assertEquals("caf%E9%2B=th%E9+%2B", body(request)); + } + } + + @Test + void defaultsToUtf8WhenCharsetIsOmitted() throws Exception { + try (UnirestInstance client = Unirest.spawnInstance()) { + MultipartBody request = client.post("http://localhost/form") + .noCharset() + .field("cafĂ©+", "thĂ© +"); + + assertEquals("caf%C3%A9%2B=th%C3%A9+%2B", body(request)); + assertEquals("application/x-www-form-urlencoded", request.getHeaders().getFirst("Content-Type")); + } + } + + @Test + void preservesRepeatedFieldNames() throws Exception { + try (UnirestInstance client = Unirest.spawnInstance()) { + MultipartBody request = client.post("http://localhost/form") + .field("a&b", "one") + .field("a&b", "two"); + + assertEquals("a%26b=one&a%26b=two", body(request)); + } + } + + private String body(MultipartBody request) throws Exception { + HttpRequest.BodyPublisher publisher = new BodyBuilder(request).getBody(); + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + CompletableFuture result = new CompletableFuture<>(); + publisher.subscribe(new Flow.Subscriber() { + @Override + public void onSubscribe(Flow.Subscription subscription) { + subscription.request(Long.MAX_VALUE); + } + + @Override + public void onNext(ByteBuffer item) { + byte[] chunk = new byte[item.remaining()]; + item.get(chunk); + bytes.write(chunk, 0, chunk.length); + } + + @Override + public void onError(Throwable error) { + result.completeExceptionally(error); + } + + @Override + public void onComplete() { + result.complete(bytes.toString(StandardCharsets.UTF_8)); + } + }); + return result.get(5, TimeUnit.SECONDS); + } +} diff --git a/unirest/src/test/java/kong/unirest/core/java/SseResponseHandlerTest.java b/unirest/src/test/java/kong/unirest/core/java/SseResponseHandlerTest.java new file mode 100644 index 000000000..aed5aea12 --- /dev/null +++ b/unirest/src/test/java/kong/unirest/core/java/SseResponseHandlerTest.java @@ -0,0 +1,251 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core.java; + +import kong.unirest.core.SseHandler; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.net.http.HttpResponse; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class SseResponseHandlerTest { + + SseResponseHandler handler; + TestHandler listener; + + @BeforeEach + void setUp() { + listener = new TestHandler(); + handler = new SseResponseHandler(null, listener); + } + + @Test + void sendComment() { + handle(": Hello World"); + listener.assertComment("Hello World"); + } + + @Test + void dontSendDataWithoutADispatch() { + handle("data: foo"); + listener.assertEventCount(0); + } + + @Test + void sendSimpleEvent() { + handle("data: foo", ""); + listener.assertEventCount(1) + .assertEvent("", "", "foo"); + } + + @Test + void dataWithJson() { + handle("data: \"foo\": 1", ""); + listener.assertEventCount(1) + .assertEvent("", "", "\"foo\": 1"); + } + + @Test + void eventWithId() { + handle("data: foo", "id: 1", ""); + listener.assertEvent("1", "", "foo"); + } + + @Test + //https://html.spec.whatwg.org/multipage/server-sent-events.html#parsing-an-event-stream + void specExample1() { + //The following event stream, once followed by a blank line: + handle("data: YHOO", + "data: +2", + "data: 10", + ""); + + //...would cause an event message with the interface MessageEvent to be dispatched on the EventSource object. + // The event's data attribute would contain the string "YHOO\n+2\n10" (where "\n" represents a newline). + listener.assertEvent("", "", "YHOO\n+2\n10"); + } + + @Test + void specExample2() { + //The following stream contains four blocks. The first block has just a comment, and will fire nothing. + // + // The second block has two fields with names "data" and "id" respectively; an event will be fired for this block, + // with the data "first event", and will then set the last event ID to "1" so that if the connection died between this block and the next, + // the server would be sent a `Last-Event-ID` header with the value `1`. + // + // The third block fires an event with data "second event", and also has an "id" field, + // this time with no value, which resets the last event ID to the empty string + // (meaning no `Last-Event-ID` header will now be sent in the event of a reconnection being attempted). + // Finally, the last block just fires an event with the data " third event" (with a single leading space character). + // Note that the last still has to end with a blank line, + // the end of the stream is not enough to trigger the dispatch of the last event. + handle(": test stream", + "", + "data: first event", + "id: 1", + "", + "data:second event", + "id", + "", + "data: third event", + "", + "data: fourth event"); + + + listener.assertComment("test stream") + .assertEvent("1", "", "first event") + .assertEvent("", "", "second event") + .assertEvent("", "", " third event") + .assertNoEventContaining("fourth event"); + } + + @Test + void specExample3() { + //The following stream fires two events: + //The first block fires events with the data set to the empty string, as would the last block if it was followed by a blank line. The middle block fires an event with the data set to a single newline character. + // The last block is discarded because it is not followed by a blank line. + handle("data", + "", + "data", + "data", + "", + "data:"); + + listener.assertEvent("", "", "") + .assertEvent("", "", "\n"); + + } + + @Test + void specExample4() { + //The following stream fires two identical events: + //This is because the space after the colon is ignored if present. + handle("data:test", + "", + "data: test", + ""); + + listener.assertEvent("", "", "test"); + } + + @Test + void streamResponseExample1() { + var stream = Stream.of("data: YHOO", + "data: +2", + "data: 10", + ""); + + var events = handler.map(stream).collect(Collectors.toList()); + + assertThat(events) + .hasSize(1) + .containsExactly(new Event("", "", "YHOO\n+2\n10")); + } + + @Test + void streamResponseExample2() { + var stream = Stream.of(": test stream", + "", + "data: first event", + "id: 1", + "", + "data:second event", + "id", + "", + "data: third event", + "", + "data: fourth event"); + + var events = handler.map(stream).collect(Collectors.toList()); + + assertThat(events) + .hasSize(3) + .containsExactly( + new Event("1", "", "first event"), + new Event("", "", "second event"), + new Event("", "", " third event") + ); + } + + private class TestHandler implements SseHandler { + + private List events = new ArrayList<>(); + private List comments = new ArrayList<>(); + + @Override + public void onEvent(Event event) { + events.add(event); + } + + @Override + public void onComment(String line) { + comments.add(line); + } + + public TestHandler assertEvent(String id, String event, String value) { + Event expected = new Event(id, event, value, null); + assertThat(events) + .contains(expected); + return this; + + } + + public TestHandler assertComment(String comment) { + assertThat(comments) + .contains(comment); + return this; + } + + public TestHandler assertNoEventContaining(String value) { + assertThat(events) + .extracting(e -> e.data()) + .doesNotContain(value); + + return this; + } + + public TestHandler assertEventCount(int count) { + assertThat(events) + .hasSize(count); + return this; + } + } + + private void handle(String... lines) { + var res = mock(HttpResponse.class); + when(res.body()).thenReturn(Stream.of(lines)); + handler.accept(res); + } + +} \ No newline at end of file diff --git a/unirest/src/test/java/kong/unirest/core/json/CoreFactoryTest.java b/unirest/src/test/java/kong/unirest/core/json/CoreFactoryTest.java new file mode 100644 index 000000000..b0ae5bceb --- /dev/null +++ b/unirest/src/test/java/kong/unirest/core/json/CoreFactoryTest.java @@ -0,0 +1,76 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package kong.unirest.core.json; + +import kong.unirest.core.UnirestConfigException; +import kong.unirest.core.UnirestException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + +class CoreFactoryTest { + + @AfterEach + void tearDown() { + try { + CoreFactory.autoConfig(); + } catch (Exception e){} + } + + @Test + void whenThereIsNoImpl() { + CoreFactory.setEngine(null); + var ex = assertThrows(UnirestConfigException.class, + () -> CoreFactory.getCore()); + assertEquals(String.format("No Json Parsing Implementation Provided%n" + + "Please add a dependency for a Unirest JSON Engine. This can be one of:%n" + + "%n" + + "%n" + + " com.konghq%n" + + " unirest-object-mappers-gson%n" + + " ${latest-version}%n" + + "%n" + + "%n" + + "%n" + + "%n" + + " com.konghq%n" + + " unirest-object-mappers-jackson%n" + + " ${latest-version}%n" + + ")%n" + + "%n" + + "Alternatively you may register your own JsonEngine directly with CoreFactory.setEngine(JsonEngine jsonEngine)"), ex.getMessage()); + } + + @Test + void canSetItManually() { + var mock = mock(JsonEngine.class); + CoreFactory.setEngine(mock); + assertThat(CoreFactory.getCore()).isSameAs(mock); + } +} \ No newline at end of file diff --git a/unirest/src/test/resources/JSON_POINTER_REF.json b/unirest/src/test/resources/JSON_POINTER_REF.json new file mode 100644 index 000000000..9fc119831 --- /dev/null +++ b/unirest/src/test/resources/JSON_POINTER_REF.json @@ -0,0 +1,19 @@ +{ + "foo": ["bar", "baz"], + "": 0, + "a/b": 1, + "c%d": 2, + "e^f": 3, + "g|h": 4, + "i\\j": 5, + "k\"l": 6, + " ": 7, + "m~n": 8, + "cucu": [ + { + "banana": { + "pants" : true + } + } + ] +} \ No newline at end of file diff --git a/unirest/src/test/resources/test-json-patch.json b/unirest/src/test/resources/test-json-patch.json new file mode 100644 index 000000000..6882fc6e2 --- /dev/null +++ b/unirest/src/test/resources/test-json-patch.json @@ -0,0 +1,8 @@ +[ + {"op":"add","path":"/fruits/-","value":"Apple"}, + {"op":"remove","path":"/bugs"}, + {"op":"replace","path":"/lastname","value":"Flintstone"}, + {"op":"test","path":"/firstname","value":"Fred"}, + {"op":"move","path":"/new/location","from":"/old/location"}, + {"op":"copy","path":"/new/location","from":"/original/location"} +] \ No newline at end of file