diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md deleted file mode 100644 index 48d5f81fa..000000000 --- a/.github/ISSUE_TEMPLATE/custom.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: Custom issue template -about: Describe this issue template's purpose here. -title: '' -labels: '' -assignees: '' - ---- - - 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..17c4acd0c --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,32 @@ +name: Verify + +on: + push: + # Sequence of patterns matched against refs/heads + branches-ignore: + - apache5 + pull_request: + +jobs: + build: + timeout-minutes: 15 + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - name: Set up JDK 1.11 + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '21' + - 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 89f4337df..3b265261c 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,6 @@ target .DS_Store .settings release.properties -META-INF .idea *.iml .gitconfig @@ -13,4 +12,8 @@ _site/ Gemfile.lock .sass-cache/ .jekyll-metadata +1.jpg +dependency-reduced-pom.xml +site +``` diff --git a/.java-version b/.java-version index 625934097..7273c0fa8 100644 --- a/.java-version +++ b/.java-version @@ -1 +1 @@ -1.8 +25 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 669cc8039..000000000 --- a/.travis.yml +++ /dev/null @@ -1,17 +0,0 @@ -language: java - -jdk: - - oraclejdk8 - -os: - - linux - -cache: - directories: - - .autoconf - - $HOME/.m2 - -# blocklist -branches: - except: - - v3 diff --git a/CHANGELOG.md b/CHANGELOG.md index 1629cb66e..1165e9801 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,439 @@ +## 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 @@ -78,7 +514,7 @@ Unirest.get("http://someplace") ## 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)``` +* Disable Automatic retries with ```Unirest.config().ÏautomaticRetries(false)``` ## 2.0.03 * Make sure the GzipInputStream gets closed. @@ -167,7 +603,7 @@ Unirest.get("http://some.file.with.windows.encoding/file.txt") ### OpenUnirest:3.2.01 * add method to replace a header rather than append to it. -### OpenUnirest:3.2.00 +### 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 @@ -176,14 +612,14 @@ File file = Unirest.get("https://someplace/file.tar.gz") .getBody(); ``` -### OpenUnirest:3.1.02 +### 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.1.00 +### 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. @@ -200,7 +636,7 @@ File file = Unirest.get("https://someplace/file.tar.gz") ### OpenUnirest:3.0.01 * Support for authenticated proxies with ```Unirest.config().proxy("proxy.server.host", 80, "username","password")``` -### OpenUnirest:3.0.00 +### 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) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 49eb51099..2ff83eb16 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,5 +30,5 @@ Include details about your configuration and environment: * 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/master/src/test/java/BehaviorTests) + * 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/README.md b/README.md index e7b8b2606..93f9e53db 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,21 @@ # Unirest for Java -[![Build Status](https://travis-ci.org/Kong/unirest-java.svg?branch=master)](https://travis-ci.org/Kong/unirest-java) -[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.konghq/unirest-java/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.kong/unirest-java) -[![Javadocs](http://www.javadoc.io/badge/com.konghq/unirest-java.svg)](http://www.javadoc.io/doc/com.konghq/unirest-java) -[![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/Kong/unirest-java) - - -## 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 - 2.3.07 - - - - - com.konghq - unirest-java - 2.3.07 - standalone - - -``` +[![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 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: + +## Documentation +Our [Documentation](http://kong.github.io/unirest-java/) ## Upgrading from Previous Versions -See the [Upgrade Guide](UPGRADE_GUIDE.md) +See the [Upgrade Guide](http://kong.github.io/unirest-java/upgrade-guide) ## ChangeLog See the [Change Log](CHANGELOG.md) for recent changes. -## Documentation -Our [Documentation](http://kong.github.io/unirest-java/) 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/UPGRADE_GUIDE.md b/UPGRADE_GUIDE.md deleted file mode 100644 index 72cc2fe01..000000000 --- a/UPGRADE_GUIDE.md +++ /dev/null @@ -1,70 +0,0 @@ -## 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. 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()``` - - -#### Unirest.config() -Unirest config allows easy access to build a configuration just like you would build a request: - -```java - Unirest.config() - .socketTimeout(500) - .connectTimeout(1000) - .concurrency(10, 5) - .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/bad-maven-test/pom.xml b/bad-maven-test/pom.xml index 3b4b6a0ce..b8851d814 100644 --- a/bad-maven-test/pom.xml +++ b/bad-maven-test/pom.xml @@ -13,8 +13,8 @@ org.apache.maven.plugins maven-compiler-plugin - 8 - 8 + 11 + 11 diff --git a/build/checkstyle.xml b/build/checkstyle.xml index 638d89f13..492bca3b4 100644 --- a/build/checkstyle.xml +++ b/build/checkstyle.xml @@ -63,7 +63,7 @@ - + @@ -78,6 +78,12 @@ --> + + + + + + @@ -136,11 +142,7 @@ - - - - - + diff --git a/build/suppressions.xml b/build/suppressions.xml index dec6b7797..59c91ffc0 100644 --- a/build/suppressions.xml +++ b/build/suppressions.xml @@ -3,7 +3,18 @@ - + + + + + + + + + + + + \ No newline at end of file diff --git a/deploy b/deploy deleted file mode 100755 index 0f74788bc..000000000 --- a/deploy +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -mvn clean verify -mvn release:prepare -DskipTests -mvn release:perform -DskipTests diff --git a/object-mapper-jackson/deploy.sh b/deploy.sh similarity index 78% rename from object-mapper-jackson/deploy.sh rename to deploy.sh index 843fa9fa6..ddb60c43f 100755 --- a/object-mapper-jackson/deploy.sh +++ b/deploy.sh @@ -25,6 +25,14 @@ # -mvn clean verify -mvn release:prepare -DskipTests -mvn release:perform -DskipTests +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/docs/Gemfile b/docs/Gemfile deleted file mode 100644 index 3a1ebf306..000000000 --- a/docs/Gemfile +++ /dev/null @@ -1,2 +0,0 @@ -source 'https://rubygems.org' -gem 'github-pages' \ No newline at end of file diff --git a/docs/_config.yml b/docs/_config.yml deleted file mode 100644 index 9c2f210a2..000000000 --- a/docs/_config.yml +++ /dev/null @@ -1,15 +0,0 @@ -# Site settings -sparkversion: 2.8.0 -templateversion: 2.7.1 -description: > # > means to ignore newlines as long as indent is kept - unirest-java - Unirest in Java: Simplified, lightweight HTTP client library. -baseurl: "/unirest-java" # the subpath of your site, e.g. /blog -url: "http://kong.github.io" # the base hostname & protocol for your site -gems: [jekyll-redirect-from] - -# Build settings -markdown: kramdown -sass: - sass_dir: _sass - style: compressed diff --git a/docs/_includes/base64/facebook.b64 b/docs/_includes/base64/facebook.b64 deleted file mode 100644 index df8168f9e..000000000 --- a/docs/_includes/base64/facebook.b64 +++ /dev/null @@ -1 +0,0 @@ -data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAApVBMVEU7WZj///8dP4guTpGmutM3VpeltNEqSo/6/P08Wpk2VJUsTJDo7PNlfa4yUZQsS48mRoxceq1ieqxddqr8/v/q9vnv8veitdB8j7tyh7VQa6RIZJ9EYZ33+Pvw9/q/yd63wdmqu9Sdsc+DmL+DlL54jbltg7NKZaAwT5IpSY4gQYn38vjk6fHi5/HZ4e3Z3uvC0OKers1lg7JkgrJgeKxacqgNMX/YOePtAAAA1klEQVRIx+3RxxKCQBAEUEDXTbBLlGgOBHP8/0/T5YI3hqNKn/vVVE1rff48rOCcY0aBdYpF7nmelSJg377OTqZpjjcYBnA01eusbVBfpAe9E7BXdZtMSFSCQLVU/WNsbXMGAsZIgSFGCPQhhJ8XBUKDYwEAKHOzuQLzxHMftP3I4D7eTxTYvXdYFe1AWnqTGPBW/gGIhQHAJcRXbZ+QwJHtgKWRO1PgvN3EGWgHZITNDl2GG2haD74LLBRYwEG19INgGsIBS26O4yQIDKgopZSCan1+KC9JdQ1nh5XlRQAAAABJRU5ErkJggg== \ No newline at end of file diff --git a/docs/_includes/base64/ghFork.b64 b/docs/_includes/base64/ghFork.b64 deleted file mode 100644 index 41289b107..000000000 --- a/docs/_includes/base64/ghFork.b64 +++ /dev/null @@ -1 +0,0 @@ -data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJUAAACVCAMAAABmfEh9AAAAllBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPEhwAAAAFBgkAAAARFR8fIiwRFB4YHCUQEx0RFB4SFiERFB8QEx3s7e4eIStPUlpKTFUvMjwOEhs8P0hfYmlXWmE5PUUZHSfd3uAnKjTAwcRnanB2eH5CRU6TlZqxs7bP0NKFh4yUlpqipKiUlpujpKiipKdzB4/DAAAAFXRSTlMAAwgBDREiKBYbL5wdQBjb3ZzclY3cWcIvAAAGZ0lEQVR42u2c3W7bMAyF02bL2q5bu7WLqDhW7Si24p902/u/3EglljxXQX1jixclsIv0QvhAHvAQZLJFF8vl1dX19afn/Ub9ej+2L5ufd6v725vPn66vr66ulsvFBOGoPj/LLJHvY8kkf/j2ZTUtlqe6ed6+JCOytX/ZPCLW/aRYnur2aZeo3QisjXogrO+TYjmq1ROqZgQWlvpxaiyisljf71dP+yQfJflk8mwtfbK+/NhnmxGSx1L/nBhraZP1ySbr7sc2O4xrEIMiTpqsu6+7zWGU5PNpi0hUfaxRkl9jqR9nxZLjJJ8d5s3W/mWU5Dfq56xY4z1x3iJuxnrinFg/xnrizJLfJZeLuMboJK9mbxDyIpPjkpvD48ySz4NQLpwnzopFY2CYqfzti7ibXfLZIQQlSoBjT1sRJL8bUgmCaiyWHwOjemKXqVIWYDwWljqmJ56h6jSVfyR+YOGJlCpbvipNhXBUbgycVfKqR3UuX0JUGNE8kcZA2VEphMLyZdIYabGieeJX74mYKyNEmiYaQCuLFdMTcy8sIUSuwWT6lYpI4SUfwRM9Vk1lbLQYYO3m90TVyxa1hgqqVEmHFdkTCaLRWQVa5rrAdMla+WzNPwYeXLaUBihyVBfUUhaAVh1zDNx1yZJllpLk20IWzqjjeaJXfK5J8ghVuQYRzxNdtpoOKt0674kjeVqNdFjqBAUatLJUMT3x0GtbCFXAqymVK2GkMZA80WH9gaqFLbVSGwxWI1ZbRhSFEAGsaKsRy7GFVlgsZT8yWI1YqkKtiUrpo+sPzhPjrEYsFv4jKGiMw/JjYCxPpCCotoDSYnFYjVgqRV3eQNNhRfdEwtoSVK51A+2wiBsVbww0BAWZBC08VuTVCAkroXQl8DdFLD6eaMCmK9+bTLzxxIdonqgIqixpGJTRPdGNgYIknwG0GZT4kcW5gLBQ8g0UugRzVhaLMVCIFF5lAXq7tsHhXGCxdCNlUboJItYYSEXMlOvxJTRK9AabqOeCg+ywslbYSExyIovuiQRxnpuPAICDDY/VSId1hCKrQHVtnsVqZJ1AIdMSTJqc2zyL1YiBhBr9voLOfTisRkqoMg2mAizksEHk8TwRxU5QhdGwdZKPey6wA7PZViQurOW6i/irESEMQckGc+UjqidaAtEiVAGtEJ7KST6KJ67dDuLYdfrWyp7BamTdnqFkDRj2vMJgNbI2lDFKmW6zGtqe5OOcC7z7EFSxRbhae6r1PoviiV71BhpJh6iyPCk++oXMUmm0HwyB8bvlcC74dXJqZLLZSkD/3yDirUYkNGmaN3maJsJkwmJFXo2c+kPdApi0hDaV557K4FyATFAilM6roupafTRPlJ3eZXGCMgAVZuuo4nnifuM9UQmCkgWQY1fQSPpjrNWIz5Y6QVVlhVBaifieaLFKldZ01SeoPBVSRfXEXYclEqgt1On0oxHrwmrEUU05BjptQZNQprStZf/uIwee6F6bYwwsa0nly7UmKHHxWyOeap4x0ICmFg9WWvSX4DcpHdWUReydC2StTpe7WuZdkw+sRgYPTrcacZonKF2i2nOkCntij2omTxQERWovpNsmvfl1weC56TzR33wwS9S5ZFoqGrjK3hioHk5Yg9em9ESnLZUa6lwlNFIc7br5/3PB7c3gsUk90WurAWnNOq3cDrzvibfDxyYdAx1WDcZ0UKStwbngPvDcHOeCGk5QUGSOynviKvDaHGOgqBHKb2wGP6i5Cz43xwldpd3GJnAuWIRj+hO6MABJKvu58p4YemtiT/RYr0b3Tyte8oGX5pK8qAGg9WL3sV1cjum/NVKabjk5iMvPTbwa6e//IlBdWo14qAhU75wLkCkC1bs/qIlFFfZEBlSBMZADVcgTGVCFJM+A6o3kk5wDVWg1woAq4IkcqEKeyIAq4Ik7BlSBMZADVWgMZEAV8EQOVCFPZEAV8EQOVAFP5EAV8kQGVIHVCAeqt5LfSAZUAU/kQBX6JiUDqtBqhAFVwBM5UIU8kQHVW8krDlShcwEDqoAncqAaYqEncqAaYpEnMqD6D2v15QnHQA5Ufaz71eqJxsAFg/BY35HriXbIHMJhEdftM+6QWUSHRVw3+L+VLXhEh4VcGM8LJnHGQjCKBZcgLOKysWATiEXBjAqxiMvGglEsMfhRWS4bC3bBk+ojPuIjPiIc/wAQkAE6ZqHsywAAAABJRU5ErkJggg== \ No newline at end of file diff --git a/docs/_includes/base64/ghStars.b64 b/docs/_includes/base64/ghStars.b64 deleted file mode 100644 index f7442b380..000000000 --- a/docs/_includes/base64/ghStars.b64 +++ /dev/null @@ -1 +0,0 @@ -data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFwAAAAUCAIAAACPhs0OAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAABRhJREFUeNrcWG1sU2UUPi33A2071w9pO5ao6VrjjFs7E8zWOiaJUCYuSCALqES2ZPOHBDuCbAEBQwwzshvWaKJ8NfgDBeofA5lfJKZFoskoBrcZ1yXiiO02dtfl3rcf97a7rz/uspS7+sPIdonnx7nJOff2nDznOafveTUYY5ZlU6mUKIqgtlAUZTQazWazwi5nKAjCkkanaVqOTrAsm81mKyoqaJrGGGs0GhV1Pp+fmprCGFsslmJEEEJ2u52iqCUFRRTF6elpANCmUimj0UhRlCRJAKCuJgjCYrGkUqniXGdmZsxm81IjIvPUbDbPzMwQoihSFIUxBgCF5nk+FovF43EAcDqddXV1BoOh5Jv3UVMUlc/nFQWkaXp5+pemaVEUiYVsFHL69OmLFy8ihBYser2+tbW1vb0d/u+ilR+Kiu3fv//s2bPFiAAAQujMmTPd3d1LypSSFVIBFEVvnzhxIhqNbtu2bdeuXTabze12ezweq9Xa1ta2cePGSCRy6tSpUhNB+P2LwMsveBsa1nd8GPlLkgDg7o3P+r9n/+1kUV0IBVOSyeSlS5cAwOPxNDY2KpolEokMDAyEQqHm5ma73X5PhRMDx4M/NwR/PFJx+Y2tPZ8+N3Dk+fHzuz8Zf3fTf2HK7OxsWVmZCkzBRRKJRGRHVVUVXiRWq3UBHaVvdjIOAJKQW/1KaHDw2LrCla7OCwA/Hd3k6x/CODd6oatlnc/n8+84/F0CYzzU7/P5urq7/b4dF27f80vFiPA8b7PZVJ4pyWQSANxut5yKooYul8vhcAAAz/NK7zNbeuqpy2+3vMVcuTWZyYnmzR+HtgPUH/56cJ8bj4YPhJ/quz44GO5YefW98AhgYgUAzK7Z88Pgl687SjBFLURKzBQ5J3nElux52YUQUnrzq5qD34SDHY/+cqz9pdf6f0VCdg4DgJRPZwWobgsdd/72wTs7Oz4aBdBq52dH3dOVmUxOKDxYM0XJFLvdDgBjY2OJRGIxU+Lx+OTkJACU4JGUz2XJijWt758PH352PHzu+vR8zTEAcFd7Wl79/E/fm30ne+rnIwIAgCRiLElSCaaUl5cbDIaJiQl1QCnuZ6/XCwBWq/XgwYOxWKzYFYvFent75c+8Xq9ipLAD+9Y2dn11O5tJJ/9IALV61SMYAEAAEWN8NzEiwhMN7koYv3WjCJN7gytnilq4lGDKhg0bnE5nTU1NIBAIBoOyvbe3NxAIjI2NAYDf71/MlFWbDxzfkjm5s6mpafflx7tCAQ+JH6vf7ho6unX9yZEnX9y7yfXtnrXr90bpJjvciI9jwAAwr//536e8vJzjuGUGRTM8PFxZWVm8lSGEAoGA3++vqalxuVyyPRqNHjp0CAAcDgfDMGVlZcqNTks99DBNaDUAAHhOzKQFiVypW0lqNTAnorxWdmJJwlqtpiAgTBjIFXN5Pp1VboZ37typrq5eSHF4eNjlci0bIqOjoyVOtHq9nmGYa9eudXZ2xmIx2a7T6QCgtraWYZjSG5AkZhDPycKnc3OAcT4rW9K5BSePEOI4PiPgbJrjuHT2QTzREvLMV9RKp9MxDHPz5k2bzSZ7rVZrX19fbW2tRqNZ/P791eqDQpJkoVAgCGJxxdxu9wKPbDZbyZPLfdeiKJIkqdjoBUFYnkVZEASKogiTycRxnF6vl3FRVwqFAkLIaDQWG00mE8uyFotlGS6ZWJY1mUyaB+o6kiRJk8mk1nUkRVFy9L8HANLhIkH01sUwAAAAAElFTkSuQmCC \ No newline at end of file diff --git a/docs/_includes/base64/twitter.b64 b/docs/_includes/base64/twitter.b64 deleted file mode 100644 index 1c9e6a3fe..000000000 --- a/docs/_includes/base64/twitter.b64 +++ /dev/null @@ -1 +0,0 @@ -data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAA2FBMVEVWo9n///9UotlSodhNntdLndZDmNVVo9lQoNhInNZap9tXpNpOn9f4+/6Ow+ZuseBCmdLx+P2jzeqLweWBvORiq91ZpdlGmtY+ltT8/v/0+/7f8Pvc7fe+3vF8ueJztOFpsN9qr9tAl9T4/f/q9fzl8/rP5/bH5PbK4/TH4/Oy1u6o0uuZyeeHv+R4uON5uN5xtNz6/f7b7vrX7PnC3/K12O6gzeyTxuibyueWx+Vkrd5JnNfi8PrY6/jV6Pe53fK42e6u1e2MwOF+u+B4t+Bus+BrsN86lNPPXfvkAAABhklEQVRIx+3T107rQBSF4T3V427c0xukNw4dTqO//xsREIhJvAfuUb5La/322JJhbw+4LEvJ4RWjUEHV1kWXySyI4ySLqKtKx6kGuUP1wru5OCEbzXlR5nHgVh6hrkeX0edVRqfkXet/s760rd1AdIgfcPZxPjonmtR5yHcLu03Icey+J8w50vbN4aTHoBpsjNZUsNegfUw0hysPOdKAbNSnIZMF55f6vtETlT0oOCVv/OldJ2NBTQsunqHK7ff8j0H9z+lYD+Z9JGC9NDgkuEAigZ364wN8P0gLJBChTwx+dwV2JOvMFEw8BgjRbRiCWQkouzuuY/uDR4EHFMJ/2Gu3IsCpfNJAPmxtKcAguiKIMwEmlMfVfSNnxsBVavFr941XHL6gbOd+qO/9xLMAxzzObc9Zt3z9/omiYJB1wnB1NaoRzd+1MO0tKzw/IduOZnADZtxbtLb+41k3Yi58gcriKbh9+0qD4XmaRRy+4VLRt6/bSbJYOn1ZmeMoL6SUtmfB3g/zAm+LHEPGGjqhAAAAAElFTkSuQmCC \ No newline at end of file diff --git a/docs/_includes/gtm.html b/docs/_includes/gtm.html deleted file mode 100644 index fff1143db..000000000 --- a/docs/_includes/gtm.html +++ /dev/null @@ -1,11 +0,0 @@ -
- - - - -
diff --git a/docs/_includes/macros/mavenDep.md b/docs/_includes/macros/mavenDep.md deleted file mode 100644 index 2ca02e30e..000000000 --- a/docs/_includes/macros/mavenDep.md +++ /dev/null @@ -1,28 +0,0 @@ -

Java

-~~~xml - - com.sparkjava - spark-core - {{site.sparkversion}} - -~~~ -

Kotlin

-~~~xml - - com.sparkjava - spark-kotlin - 1.0.0-alpha - -~~~ - -[Not familiar with Maven? Click here for more detailed instructions.](/tutorials/maven-setup) - -### Other dependency managers: -
-~~~java -Gradle : compile "com.sparkjava:spark-core:{{site.sparkversion}}" // add to build.gradle (for Java users) -Gradle : compile "com.sparkjava:spark-kotlin:1.0.0-alpha" // add to build.gradle (for Kotlin users) - Ivy : // ivy.xml - SBT : libraryDependencies += "com.sparkjava" % "spark-core" % "{{site.sparkversion}}" // build.sbt -~~~ -
\ No newline at end of file diff --git a/docs/_includes/macros/mavenLink.html b/docs/_includes/macros/mavenLink.html deleted file mode 100644 index a90f4a647..000000000 --- a/docs/_includes/macros/mavenLink.html +++ /dev/null @@ -1,2 +0,0 @@ -Spark {{include.version}} is available for download on -Maven Central. \ No newline at end of file diff --git a/docs/_includes/macros/mavenLinkKotlin.html b/docs/_includes/macros/mavenLinkKotlin.html deleted file mode 100644 index 5b1982ea5..000000000 --- a/docs/_includes/macros/mavenLinkKotlin.html +++ /dev/null @@ -1,2 +0,0 @@ -Spark-kotlin {{include.version}} is available for download on -Maven Central. \ No newline at end of file diff --git a/docs/_includes/macros/seeCommitHistory.html b/docs/_includes/macros/seeCommitHistory.html deleted file mode 100644 index 1b1befb25..000000000 --- a/docs/_includes/macros/seeCommitHistory.html +++ /dev/null @@ -1,2 +0,0 @@ -See the -commit history on GitHub for details. \ No newline at end of file diff --git a/docs/_includes/macros/teamMember.html b/docs/_includes/macros/teamMember.html deleted file mode 100644 index 7a13176c0..000000000 --- a/docs/_includes/macros/teamMember.html +++ /dev/null @@ -1,26 +0,0 @@ -{% assign _ = include %} -
- -
-

{{_.firstName}} {{_.lastName}}

- {{_.role}} - -
-
\ No newline at end of file diff --git a/docs/_includes/socialButtons.html b/docs/_includes/socialButtons.html deleted file mode 100644 index e596f0ecd..000000000 --- a/docs/_includes/socialButtons.html +++ /dev/null @@ -1,11 +0,0 @@ - - \ No newline at end of file diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html deleted file mode 100644 index 31cfde5e9..000000000 --- a/docs/_layouts/default.html +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - {% if page.title %}{{ page.title }} - {% endif %}Unirest in Java: Simplified, lightweight HTTP client library. - - - - - - - - - - - - Fork me on GitHub - -
- -
-
- {{ content }} -
-
- Unirest-Java is a free and open source HTTP client, released under the - MIT License -  |  Contact -  |  Team -
- - - diff --git a/docs/_sass/_contact.scss b/docs/_sass/_contact.scss deleted file mode 100644 index 3ca949cf3..000000000 --- a/docs/_sass/_contact.scss +++ /dev/null @@ -1,90 +0,0 @@ -.team { - margin-top: 20px; - width: 100%; - overflow: auto; - .team-member { - margin: 10px 0; - font-size: 14px; - width: 48%; - padding: 15px; - background: #f5f5f5; - border: 1px solid #ddd; - float: left; - overflow: auto; - &:nth-child(even) { - margin-left: 2%; - } - &:nth-child(odd) { - margin-right: 2%; - } - > img { - border-radius: 3px; - float: left; - width: 100px; - margin-right: 15px; - } - .info-wrap { - float: left; - h3 { - margin: 8px 0 0; - } - img { - display: inline-block; - opacity: 0.4; - margin-top: 10px; - width: 30px; - margin-right: 8px; - &:hover { - opacity: 0.7; - } - } - } - } -} - -@media (max-width: 800px) { - .team { - .team-member { - width: 100%; - &:nth-child(even), - &:nth-child(odd) { - margin: 0; - } - + .team-member { - margin-top: 20px; - } - } - } -} - -ul.contributors { - padding: 0; - margin: 0; - list-style: none; - display: flex; - flex-wrap: wrap; - justify-content: space-between; - li { - position: relative; - display: inline-block; - margin: 10px 5px; - border: 1px solid #000; - width: 110px; - } - img { - display: block; - width: 100%; - } - span { - padding: 1px 3px; - display: inline-block; - width: 100%; - background: linear-gradient(to right, rgba(0,0,0,1), rgba(0,0,0,0.5)); - position: absolute; - bottom: 0; - font-size: 12px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } -} \ No newline at end of file diff --git a/docs/_sass/_frame.scss b/docs/_sass/_frame.scss deleted file mode 100644 index 35c6f96d6..000000000 --- a/docs/_sass/_frame.scss +++ /dev/null @@ -1,211 +0,0 @@ -$spark-orange: #e88300; - -html { - position: relative; - min-height: 100%; -} - -body { - padding-top: 80px; - margin-bottom: 60px; -} - -header { - z-index: 1; - background: #fff; - position: fixed; - height: 80px; - width: 100%; - top: 0; - left: 0; - box-shadow: 0 1px 2px rgba(0,0,0,0.3); -} - -footer { - font-size: 13px; - position: absolute; - left: 0; - bottom: 0; - height: 40px; - width: 100%; - line-height: 40px; - text-align: center; - background: #f5f5f5; - border-top: 1px solid #ddd; - padding: 0 20px; - overflow: hidden; -} - -.width-limit { - width: 100%; - padding-left: 20px; - padding-right: 20px; - max-width: 840px; - margin: 0 auto; -} - -nav { - height: 80px; - position: relative; - #logo img { - margin: 10px 0; - height: 60px; - } - ul { - padding: 0; - margin: 0; - list-style: none; - float: right; - height: 40px; - margin-top: 40px; - li { - display: inline-block; - a { - display: block; - color: #333; - text-decoration: none; - margin: 0 5px; - padding: 0 5px; - line-height: 40px; - } - &:last-of-type a { - padding-right: 0; - margin-right: 0; - } - } - } -} - -#some-buttons { - position: absolute; - top: 15px; - right: 20px; - #githubStar { - display: inline-block; - width: 95px; - height: 20px; - position: relative; - #starBg, - #starFrame { - position: absolute; - border: 0; - } - } - a { - display: inline-block; - &:last-of-type { - margin-left: 3px; - } - img { - border-radius: 3px; - height: 20px; - } - } -} - -.right-menu { - position: fixed; - left: 50%; - list-style: none; - padding: 0; - margin-left: 430px; - top: 120px; - z-index: 999999; - width: 200px; - ul { - margin: 0; - list-style: none; - padding: 0; - .active > a { - font-weight: 700; - } - a { - display: inline-block; - padding: 1px 0; - margin: 1px 0; - } - ul { - font-size: 14px; - padding-left: 15px; - } - } - ul.tutorial-menu { - font-size: 13px; - font-family: Tahoma, arial, sans-serif; - a { - padding: 2px 0; - margin: 2px 0; - } - } -} - -@media (max-height: 850px) { - .right-menu { - font-size: 14px; - } -} - -@media (max-height: 750px) { - .right-menu { - ul a { - margin: 0; - } - } -} - -@media (max-width: 1200px) { - #fork-me { - display: none; - } -} - -@media (min-width: 701px) and (max-width: 1200px) { - main.width-limit, - nav.width-limit { - max-width: 100%; - } - .has-right-menu { - &.width-limit { - padding-right: 220px; - } - } - .right-menu { - top: 80px; - right: 0; - left: auto; - margin-left: 0; - height: 100%; - border-left: 1px solid #e0e0e0; - background: linear-gradient(to right, #f9f9f9, white); - padding: 40px 15px 0 15px; - z-index: 0; - } -} -@media (max-width: 700px) { - .right-menu { - display: none; - } -} -@media (max-width: 600px) { - a[href="/download"] { - display: none; - } -} -@media (max-width: 480px) { - nav ul li a { - margin: 0 2px; - font-size: 14px; - } -} -@media (max-width: 400px) { - nav { - ul li a { - padding: 0; - font-size: 12px; - } - #logo img { - margin: 30px 0 10px; - height: 40px; - } - } -} diff --git a/docs/_sass/_main.scss b/docs/_sass/_main.scss deleted file mode 100644 index bd755fd0a..000000000 --- a/docs/_sass/_main.scss +++ /dev/null @@ -1,166 +0,0 @@ -$spark-orange: #e88300; - -* { - box-sizing: border-box; -} - -html { - overflow-y: scroll; - color: #333; - line-height: 1.4; -} - -a { - color: $spark-orange; - text-decoration: none; -} - -h1 { font-size: 36px; } -h2 { font-size: 28px; } -h3 { font-size: 22px; } -h4 { font-size: 20px; } - -h1, -h2, -h3, -h4 { - font-weight: 700; - font-family: 'Lato', sans-serif; - font-style: italic; - letter-spacing: -0.5px; - margin-top: 1.75em; - margin-bottom: 0.75em; - + p { - margin-top: 0.25em; - } -} - -h2 { - font-weight: 400; -} - -p { - margin: 1.5em 0 0.25em; - font-family: 'Lato', sans-serif; -} - -p, -li { - code { - font-family: 'Fira Mono', monospace; - font-size: 80%; - border: 1px solid #ccc; - background: #f5f5f5; - padding: 1px 3px; - color: #0008bc; - border-radius: 3px; - font-style: normal; - } -} - -.star-one { - margin: 15px 0; - background: #fff; - padding: 15px; - border: 1px dashed #bbb; - p { - margin: 0; - } -} - -.star-two { - overflow: auto; - margin: 25px 0 10px; - > * { - height: 30px; - line-height: 30px; - float: left; - margin-right: 10px; - } -} - -pre { - margin: .5em 0; - background: #333; - padding: 15px; - border-radius: 5px; - color: #f5f5f5; - overflow-x: scroll; -} - -main { - padding: 40px 0; - img { - width: 100%; - display: block; - max-width: 100%; - } -} - -.notification { - margin: 15px 0; - background: #f5f5f5; - padding: 15px; - border-left: 5px solid $spark-orange; - p { - margin: 0; - } -} - -.template-engine { - margin-top: 32px; - h2, - p { - margin: 5px 0; - } -} - -.template-engine-list { - color: #777; -} - -.small-list { - font-size: 80%; -} - -blockquote { - background: #f9f9f9; - border-left: 10px solid #ccc; - margin: 0 0 20px 0; - padding: 0.5em 10px; - quotes: "\201C""\201D""\2018""\2019"; - &:before { - color: #ccc; - content: open-quote; - font-size: 4em; - line-height: 0.1em; - margin-right: 5px; - vertical-align: -0.5em; - } - p { - line-height: 200%; - display: inline; - } -} - -pre { // scrollbar in code examples - &::-webkit-scrollbar { - width: 5px; - height: 5px; - } - &::-webkit-scrollbar-thumb { - background: #777; - border-radius: 5px; - } - &::-webkit-scrollbar-thumb:hover { - background: #999; - } - &::-webkit-scrollbar-track { - background: #333; - border-radius: 5px; - } - &::-webkit-scrollbar-button { - width: 8px; - height: 8px; - } -} diff --git a/docs/_sass/_modifiers.scss b/docs/_sass/_modifiers.scss deleted file mode 100644 index 4fe3d9209..000000000 --- a/docs/_sass/_modifiers.scss +++ /dev/null @@ -1,3 +0,0 @@ -.no-margin-top { - margin-top: 0; -} \ No newline at end of file diff --git a/docs/_sass/_normalize.scss b/docs/_sass/_normalize.scss deleted file mode 100644 index b9d2f75d4..000000000 --- a/docs/_sass/_normalize.scss +++ /dev/null @@ -1,461 +0,0 @@ -/*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */ - -/** - * 1. Change the default font family in all browsers (opinionated). - * 2. Correct the line height in all browsers. - * 3. Prevent adjustments of font size after orientation changes in - * IE on Windows Phone and in iOS. - */ - -/* Document - ========================================================================== */ - -html { - font-family: sans-serif; /* 1 */ - line-height: 1.25; /* 2 */ - -ms-text-size-adjust: 100%; /* 3 */ - -webkit-text-size-adjust: 100%; /* 3 */ -} - -/* Sections - ========================================================================== */ - -/** - * Remove the margin in all browsers (opinionated). - */ - -body { - margin: 0; -} - -/** - * Add the correct display in IE 9-. - */ - -article, -aside, -footer, -header, -nav, -section { - display: block; -} - -/** - * Correct the font size and margin on `h1` elements within `section` and - * `article` contexts in Chrome, Firefox, and Safari. - */ - -h1 { - font-size: 2em; - margin: 0.67em 0; -} - -/* Grouping content - ========================================================================== */ - -/** - * Add the correct display in IE 9-. - * 1. Add the correct display in IE. - */ - -figcaption, -figure, -main { /* 1 */ - display: block; -} - -/** - * Add the correct margin in IE 8. - */ - -figure { - margin: 1em 40px; -} - -/** - * 1. Add the correct box sizing in Firefox. - * 2. Show the overflow in Edge and IE. - */ - -hr { - box-sizing: content-box; /* 1 */ - height: 0; /* 1 */ - overflow: visible; /* 2 */ -} - -/** - * 1. Correct the inheritance and scaling of font size in all browsers. - * 2. Correct the odd `em` font sizing in all browsers. - */ - -pre { - font-family: monospace, monospace; /* 1 */ - font-size: 1em; /* 2 */ -} - -/* Text-level semantics - ========================================================================== */ - -/** - * 1. Remove the gray background on active links in IE 10. - * 2. Remove gaps in links underline in iOS 8+ and Safari 8+. - */ - -a { - background-color: transparent; /* 1 */ - -webkit-text-decoration-skip: objects; /* 2 */ -} - -/** - * Remove the outline on focused links when they are also active or hovered - * in all browsers (opinionated). - */ - -a:active, -a:hover { - outline-width: 0; -} - -/** - * 1. Remove the bottom border in Firefox 39-. - * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. - */ - -abbr[title] { - border-bottom: none; /* 1 */ - text-decoration: underline; /* 2 */ - text-decoration: underline dotted; /* 2 */ -} - -/** - * Prevent the duplicate application of `bolder` by the next rule in Safari 6. - */ - -b, -strong { - font-weight: inherit; -} - -/** - * Add the correct font weight in Chrome, Edge, and Safari. - */ - -b, -strong { - font-weight: bolder; -} - -/** - * 1. Correct the inheritance and scaling of font size in all browsers. - * 2. Correct the odd `em` font sizing in all browsers. - */ - -code, -kbd, -samp { - font-family: monospace, monospace; /* 1 */ - font-size: 1em; /* 2 */ -} - -/** - * Add the correct font style in Android 4.3-. - */ - -dfn { - font-style: italic; -} - -/** - * Add the correct background and color in IE 9-. - */ - -mark { - background-color: #ff0; - color: #000; -} - -/** - * Add the correct font size in all browsers. - */ - -small { - font-size: 80%; -} - -/** - * Prevent `sub` and `sup` elements from affecting the line height in - * all browsers. - */ - -sub, -sup { - font-size: 75%; - line-height: 0; - position: relative; - vertical-align: baseline; -} - -sub { - bottom: -0.25em; -} - -sup { - top: -0.5em; -} - -/* Embedded content - ========================================================================== */ - -/** - * Add the correct display in IE 9-. - */ - -audio, -video { - display: inline-block; -} - -/** - * Add the correct display in iOS 4-7. - */ - -audio:not([controls]) { - display: none; - height: 0; -} - -/** - * Remove the border on images inside links in IE 10-. - */ - -img { - border-style: none; -} - -/** - * Hide the overflow in IE. - */ - -svg:not(:root) { - overflow: hidden; -} - -/* Forms - ========================================================================== */ - -/** - * 1. Change the font styles in all browsers (opinionated). - * 2. Remove the margin in Firefox and Safari. - */ - -button, -input, -optgroup, -select, -textarea { - font-family: sans-serif; /* 1 */ - font-size: 100%; /* 1 */ - line-height: 1.15; /* 1 */ - margin: 0; /* 2 */ -} - -/** - * Show the overflow in IE. - * 1. Show the overflow in Edge. - */ - -button, -input { /* 1 */ - overflow: visible; -} - -/** - * Remove the inheritance of text transform in Edge, Firefox, and IE. - * 1. Remove the inheritance of text transform in Firefox. - */ - -button, -select { /* 1 */ - text-transform: none; -} - -/** - * 1. Prevent a WebKit bug where (2) destroys native `audio` and `video` - * controls in Android 4. - * 2. Correct the inability to style clickable types in iOS and Safari. - */ - -button, -html [type="button"], /* 1 */ -[type="reset"], -[type="submit"] { - -webkit-appearance: button; /* 2 */ -} - -/** - * Remove the inner border and padding in Firefox. - */ - -button::-moz-focus-inner, -[type="button"]::-moz-focus-inner, -[type="reset"]::-moz-focus-inner, -[type="submit"]::-moz-focus-inner { - border-style: none; - padding: 0; -} - -/** - * Restore the focus styles unset by the previous rule. - */ - -button:-moz-focusring, -[type="button"]:-moz-focusring, -[type="reset"]:-moz-focusring, -[type="submit"]:-moz-focusring { - outline: 1px dotted ButtonText; -} - -/** - * Change the border, margin, and padding in all browsers (opinionated). - */ - -fieldset { - border: 1px solid #c0c0c0; - margin: 0 2px; - padding: 0.35em 0.625em 0.75em; -} - -/** - * 1. Correct the text wrapping in Edge and IE. - * 2. Correct the color inheritance from `fieldset` elements in IE. - * 3. Remove the padding so developers are not caught out when they zero out - * `fieldset` elements in all browsers. - */ - -legend { - box-sizing: border-box; /* 1 */ - color: inherit; /* 2 */ - display: table; /* 1 */ - max-width: 100%; /* 1 */ - padding: 0; /* 3 */ - white-space: normal; /* 1 */ -} - -/** - * 1. Add the correct display in IE 9-. - * 2. Add the correct vertical alignment in Chrome, Firefox, and Opera. - */ - -progress { - display: inline-block; /* 1 */ - vertical-align: baseline; /* 2 */ -} - -/** - * Remove the default vertical scrollbar in IE. - */ - -textarea { - overflow: auto; -} - -/** - * 1. Add the correct box sizing in IE 10-. - * 2. Remove the padding in IE 10-. - */ - -[type="checkbox"], -[type="radio"] { - box-sizing: border-box; /* 1 */ - padding: 0; /* 2 */ -} - -/** - * Correct the cursor style of increment and decrement buttons in Chrome. - */ - -[type="number"]::-webkit-inner-spin-button, -[type="number"]::-webkit-outer-spin-button { - height: auto; -} - -/** - * 1. Correct the odd appearance in Chrome and Safari. - * 2. Correct the outline style in Safari. - */ - -[type="search"] { - -webkit-appearance: textfield; /* 1 */ - outline-offset: -2px; /* 2 */ -} - -/** - * Remove the inner padding and cancel buttons in Chrome and Safari on macOS. - */ - -[type="search"]::-webkit-search-cancel-button, -[type="search"]::-webkit-search-decoration { - -webkit-appearance: none; -} - -/** - * 1. Correct the inability to style clickable types in iOS and Safari. - * 2. Change font properties to `inherit` in Safari. - */ - -::-webkit-file-upload-button { - -webkit-appearance: button; /* 1 */ - font: inherit; /* 2 */ -} - -/* Interactive - ========================================================================== */ - -/* - * Add the correct display in IE 9-. - * 1. Add the correct display in Edge, IE, and Firefox. - */ - -details, /* 1 */ -menu { - display: block; -} - -/* - * Add the correct display in all browsers. - */ - -summary { - display: list-item; -} - -/* Scripting - ========================================================================== */ - -/** - * Add the correct display in IE 9-. - */ - -canvas { - display: inline-block; -} - -/** - * Add the correct display in IE. - */ - -template { - display: none; -} - -/* Hidden - ========================================================================== */ - -/** - * Add the correct display in IE 10-. - */ - -[hidden] { - display: none; -} \ No newline at end of file diff --git a/docs/_sass/_prism.scss b/docs/_sass/_prism.scss deleted file mode 100644 index 7ad7dcb96..000000000 --- a/docs/_sass/_prism.scss +++ /dev/null @@ -1,181 +0,0 @@ -/* http://prismjs.com/download.html?themes=prism-okaidia&languages=markup+css+clike+javascript+bash+ruby+java+kotlin+sql&plugins=toolbar+copy-to-clipboard */ -/** - * okaidia theme for JavaScript, CSS and HTML - * Loosely based on Monokai textmate theme by http://www.monokai.nl/ - * @author ocodia - */ - -code[class*="language-"], -pre[class*="language-"] { - color: #f8f8f2; - background: none; - text-shadow: 0 1px rgba(0, 0, 0, 0.3); - font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; - text-align: left; - white-space: pre; - word-spacing: normal; - word-break: normal; - word-wrap: normal; - line-height: 1.5; - - -moz-tab-size: 4; - -o-tab-size: 4; - tab-size: 4; - - -webkit-hyphens: none; - -moz-hyphens: none; - -ms-hyphens: none; - hyphens: none; -} - -/* Code blocks */ -pre[class*="language-"] { - padding: 1em; - margin: .5em 0; - overflow: auto; - border-radius: 0.3em; -} - -:not(pre) > code[class*="language-"], -pre[class*="language-"] { - background: #272822; -} - -/* Inline code */ -:not(pre) > code[class*="language-"] { - padding: .1em; - border-radius: .3em; - white-space: normal; -} - -.token.comment, -.token.prolog, -.token.doctype, -.token.cdata { - color: slategray; -} - -.token.punctuation { - color: #f8f8f2; -} - -.namespace { - opacity: .7; -} - -.token.property, -.token.tag, -.token.constant, -.token.symbol, -.token.deleted { - color: #f92672; -} - -.token.boolean, -.token.number { - color: #ae81ff; -} - -.token.selector, -.token.attr-name, -.token.string, -.token.char, -.token.builtin, -.token.inserted { - color: #a6e22e; -} - -.token.operator, -.token.entity, -.token.url, -.language-css .token.string, -.style .token.string, -.token.variable { - color: #f8f8f2; -} - -.token.atrule, -.token.attr-value, -.token.function { - color: #e6db74; -} - -.token.keyword { - color: #66d9ef; -} - -.token.regex, -.token.important { - color: #fd971f; -} - -.token.important, -.token.bold { - font-weight: bold; -} -.token.italic { - font-style: italic; -} - -.token.entity { - cursor: help; -} - -pre.code-toolbar { - position: relative; -} - -pre.code-toolbar > .toolbar { - position: absolute; - top: .3em; - right: .2em; - transition: opacity 0.3s ease-in-out; - opacity: 0; -} - -pre.code-toolbar:hover > .toolbar { - opacity: 1; -} - -pre.code-toolbar > .toolbar .toolbar-item { - display: inline-block; -} - -pre.code-toolbar > .toolbar a { - cursor: pointer; -} - -pre.code-toolbar > .toolbar button { - background: none; - border: 0; - color: inherit; - font: inherit; - line-height: normal; - overflow: visible; - padding: 0; - -webkit-user-select: none; /* for button */ - -moz-user-select: none; - -ms-user-select: none; -} - -pre.code-toolbar > .toolbar a, -pre.code-toolbar > .toolbar button, -pre.code-toolbar > .toolbar span { - color: #bbb; - font-size: .8em; - padding: 0 .5em; - background: #f5f2f0; - background: rgba(224, 224, 224, 0.2); - box-shadow: 0 2px 0 0 rgba(0,0,0,0.2); - border-radius: .5em; -} - -pre.code-toolbar > .toolbar a:hover, -pre.code-toolbar > .toolbar a:focus, -pre.code-toolbar > .toolbar button:hover, -pre.code-toolbar > .toolbar button:focus, -pre.code-toolbar > .toolbar span:hover, -pre.code-toolbar > .toolbar span:focus { - color: inherit; - text-decoration: none; -} diff --git a/docs/_sass/_prismOverrides.scss b/docs/_sass/_prismOverrides.scss deleted file mode 100644 index 8e50be283..000000000 --- a/docs/_sass/_prismOverrides.scss +++ /dev/null @@ -1,55 +0,0 @@ -.highlighter-rouge { - margin: 10px 0; - position: relative; - overflow: auto; - pre { - margin: 0; - overflow: auto; - background: #30312a; - padding: 15px; - code { - float: left; - font-family: 'Fira Mono', monospace; - font-size: 15px; - line-height: 1.6; - } - } -} - -.smaller-code { - pre code { - font-size: 13px; - } -} - -// Toolbar - -pre.code-toolbar { - position: initial; -} - -pre.code-toolbar > .toolbar { - opacity: 1; - transition: none; - top: 0px; - right: 0px; -} - -pre.code-toolbar > .toolbar a { - font-size: 13px; - padding: 1px 6px 3px; -} - -pre.code-toolbar > .toolbar a, -pre.code-toolbar > .toolbar button, -pre.code-toolbar > .toolbar span { - display: inline-block; - padding: 2px 8px; - background: #30312a; - box-shadow: none; - border-radius: 0 5px 0 5px; - color: #888; - border: 1px solid #444; - border-top: 0; - border-right: 0; -} diff --git a/docs/_sass/_tutorial.scss b/docs/_sass/_tutorial.scss deleted file mode 100644 index 69233937b..000000000 --- a/docs/_sass/_tutorial.scss +++ /dev/null @@ -1,67 +0,0 @@ -.tutorial { - h1, h2, h3, h4 { - font-family: "Trebuchet MS", Helvetica, sans-serif; - font-weight: 400; - } - h1 { font-size: 26px; } - h2 { font-size: 22px; } - h3 { font-size: 20px; } - h4 { font-size: 18px; } - #timeToRead { - float: right; - } - .tutorial-header .notification { - font-size: 14px; - margin-bottom: 30px; - .github-link { - display: block; - width: 100%; - padding-top: 8px; - margin-top: 8px; - border-top: 1px solid #ddd; - } - } -} - -.tutorial-overview { - overflow: hidden; - margin: 24px 0; - padding: 24px 0; - border-bottom: 1px solid #ddd; - border-top: 1px solid #ddd; -} - -.tutorials-header, -.tutorials-footer { - clear: both; - p { - margin: 0; - } -} - -ul.tutorial-list { - list-style: none; - padding: 0; - margin: 0; - width: 50%; - float: left; - h2 { - margin-top: 0; - } - li { - padding-right: 24px; - + li { - margin-top: 24px; - } - h2 { - font-size: 20px; - max-width: 100%; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - p { - font-size: 13px; - } - } -} \ No newline at end of file diff --git a/docs/css/main.scss b/docs/css/main.scss deleted file mode 100644 index dcc3b2dd7..000000000 --- a/docs/css/main.scss +++ /dev/null @@ -1,12 +0,0 @@ ---- -# Only the main Sass file needs front matter (the dashes are enough) ---- - -@import 'normalize'; -@import 'main'; -@import 'frame'; -@import 'tutorial'; -@import 'contact'; -@import 'modifiers'; -@import 'prism'; -@import 'prismOverrides'; diff --git a/docs/img/email.svg b/docs/img/email.svg deleted file mode 100644 index 9d45484cf..000000000 --- a/docs/img/email.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - diff --git a/docs/img/favicon.png b/docs/img/favicon.png deleted file mode 100644 index 6bc913c0e..000000000 Binary files a/docs/img/favicon.png and /dev/null differ diff --git a/docs/img/github.svg b/docs/img/github.svg deleted file mode 100644 index c3179e3b4..000000000 --- a/docs/img/github.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/docs/img/kong.png b/docs/img/kong.png deleted file mode 100644 index f27038eab..000000000 Binary files a/docs/img/kong.png and /dev/null differ diff --git a/docs/img/linkedin.svg b/docs/img/linkedin.svg deleted file mode 100644 index 559aad145..000000000 --- a/docs/img/linkedin.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - diff --git a/docs/img/paper.jpg b/docs/img/paper.jpg deleted file mode 100644 index 178f899e5..000000000 Binary files a/docs/img/paper.jpg and /dev/null differ diff --git a/docs/img/twitter.svg b/docs/img/twitter.svg deleted file mode 100644 index dbd7b8a4c..000000000 --- a/docs/img/twitter.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index b7c4b9f10..000000000 --- a/docs/index.md +++ /dev/null @@ -1,492 +0,0 @@ ---- -layout: default -title: Documentation -rightmenu: true ---- - -
-* [Requests](#requests) -* [Route Parameters](#route-parameters) -* [Query Parameters](#query-parameters) -* [Headers](#headers) -* [Basic Authentication](#basic-authentication) -* [Body Data](#body-data) -* [Entity Bodies](#entity-bodies) -* [JSON Patch Bodies](#json-patch-bodies) -* [Basic Forms](#basic-forms) -* [File Uploads](#file-uploads) -* [Upload Progress Monitoring](#upload-progress-monitoring) -* [Asynchronous Requests](#asynchronous-requests) -* [Paged Requests](#paged-requests) -* [Responses](#responses) -* [Empty Responses](#empty-responses) -* [String Responses](#string-responses) -* [Object Mapped Responses](#object-mapped-responses) -* [File Responses](#file-responses) -* [JSON Responses](#json-responses) -* [Large Responses](#large-responses) -* [Error Handling](#error-handling) -* [Configuration](#configuration) - * [Config Options](#config-options) - * [Custom Apache Clients](#custom-apache-clients) - * [Multiple Configurations](#multiple-configurations) -* [Shutting Down](#shutting-down) -
- -

Documentation

- -### 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 - 2.3.07 - - - - - com.konghq - unirest-java - 2.3.07 - standalone - - -``` - -### 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. - -# 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://httpbin.org/post") - .header("accept", "application/json") - .queryString("apiKey", "123") - .field("parameter", "value") - .field("foo", "bar") - .asString(); -``` - -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://httpbin.org/{fruit}") - .routeParam("fruit", "apple") - .asString(); - -// Results in `http://httpbin.org/apple` -``` -Basically the placeholder `{method}` will be replaced with `apple`. - -The placeholder's format is as easy as: `{custom_name}` - -All param values will be URL-Encoded for you - -## Query Parameters -Query-string params can be built up one by one - -```java -Unirest.get("http://httpbin.org") - .queryString("fruit", "apple") - .queryString("droid", "R2D2") - .asString(); - -// Results in "http://httpbin.org?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://httpbin.org") - .queryString("fruit", Arrays.asList("apple", "orange")) - .queryString(ImmutableMap.of("droid", "R2D2", "beatle", "Ringo")) - .asString(); - - // Results in "http://httpbin.org?fruit=apple&fruit=orange&droid=R2D2&beatle=Ringo" -``` - -## Headers -Request headers can be added with the ```header``` method. -```java -Unirest.get("http://httpbin.org") - .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://httpbin.org") - .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://httpbin.org") - .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. - -```java -// Object Mappers can be configured globally, per config, or on a per-request basis. -// We will set it up in the global config here: - -Unirest.config().setObjectMapper(new JacksonObjectMapper()); - -Unirest.post("http://httpbin.org") - .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 http://jsonpatch.com/) -Per the spec, the default ```Content-Type``` for json-patch is ```application/json-patch+json``` - -```java - Unirest.jsonPatch("http://httpbin.org") - .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://httpbin.org") - .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://httpbin.org") - .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://httpbin.org") - .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://httpbin.org") - .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://httpbin.org/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") - ); - -``` - - -# Responses -Unirest makes the actual request the moment you invoke of it's ```as[type]``` method. These methods also inform Unirest what type to map the response to. Options are ```Empty```, ```String```, ```File```, ```Object```, 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 ```.body()``` method. - -## Empty Responses -If you aren't expecting a body back, ```asEmpty``` is the easiest choice. You will still get back response information like status and headers. - -```java -HttpResponse response = Unirest.delete("http://httpbin.org").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://httpbin.org") - .asString() - .body(); -``` - -## 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 a implementation of ```ObjectMapper``` (see [Object Mappers](#object-mappers) for details.). Unirest has several modules that can be included for popular JSON object mappers like Jackson and GSON. - -Before an `asObject(Class)` it 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. - -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, serializing Json from\to Object using the popular Jackson ObjectMapper takes only few lines - -```java -// Only one time -Unirest.config().setObjectMapper(new JacksonObjectMapper()); - -// Response to Object -Book book = Unirest.get("http://httpbin.org/books/1") - .asObject(Book.class) - .getBody(); - -Author author = Unirest.get("http://httpbin.org/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. -``` - -## 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(); -``` - -## 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") - .getBody() - .getObject() - .getJSONObject("thing") - .getJSONArray("foo") - .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("foo", "bar") - .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()); - }); - }); -``` - -# 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() - .socketTimeout(500) - .connectTimeout(1000) - .concurrency(10, 5) - .proxy(new Proxy("https://proxy")) - .setDefaultHeader("Accept", "application/json") - .followRedirects(false) - .enableCookieManagement(false) - .addInterceptor(new MyCustomInterceptor()); -``` - -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. - - - -## Config Options - -| Builder Method | Impact | Default | -| ------------- | ------------- | ------------- | -| ```socketTimeout(int)``` | Sets the socket timeout for all requests in millis | 60000 | -| ```connectTimeout(int)``` | Sets the connection timeout for all requests in millis | 10000 | -| ```concurrency(int, int)``` | Sets concurrency rates; max total, max per route | 200, 20 | -| ```proxy(proxy)``` | Sets a proxy object for negotiating proxy servers. Can include auth credentials | | -| ```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 | | -| ```followRedirects(boolean)``` | toggle following redirects | true | -| ```enableCookieManagement(boolean)``` | toggle accepting and storing cookies | true | -| ```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 | -| ```automaticRetries(boolean)``` | toggle disabling automatic retries (up to 4 times) for socket timeouts | true | -| ```verifySsl(boolean)``` |toggle enforcing SSL | true | -| ```addShutdownHook(boolean)``` | toggle to add the clients to the system shutdown hooks automatically | false | -| ```clientCertificateStore(String,String)``` | Add a PKCS12 KeyStore by path for doing client certificates | | -| ```clientCertificateStore(KeyStore,String)``` | Add a PKCS12 KeyStore for doing client certificates | | - - -## Custom Apache Clients -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 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(); -``` - -**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();``` - -# Object Mappers -Unirest offers a few different Object Mapper's based on popular JSON libraries (Jackson and GSON). These can be included either as traditional or shaded jars: -``` - - - com.konghq - unirest-objectmapper-jackson - 2.3.07 - - - - - - com.konghq - unirest-object-mappers-gson - 2.3.07 - -``` - -If you have some other need you can supply your own Object mapper by implementing the ```ObjectMapper``` interface. It has only a few methods - - -# Shutting Down - -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(); -``` - -Once shutdown, using Unirest again will re-init the system - - diff --git a/docs/js/_partials/main.js b/docs/js/_partials/main.js deleted file mode 100644 index 2bff6435a..000000000 --- a/docs/js/_partials/main.js +++ /dev/null @@ -1,40 +0,0 @@ -// add target blank to all external links -for (var i = 0; i < document.links.length; i++) { - if (document.links[i].hostname != window.location.hostname) { - document.links[i].target = "_blank"; - } -} - -var offset = 24; -var fixedHeaderSelector = 'body > header'; -smoothScroll.init({ // https://github.com/cferdinandi/smooth-scroll - selector: 'a[href^="#"]', // Selector for links (must be a class, ID, data attribute, or element tag) - selectorHeader: fixedHeaderSelector, // Selector for fixed headers [optional] - speed: 500, // Integer. How fast to complete the scroll in milliseconds - easing: 'easeInOutCubic', // Easing pattern to use - offset: offset, // Integer. How far to offset the scrolling anchor location in pixels - callback: function ( anchor, toggle ) {} // Function to run after scrolling -}); - -// scroll to current element on load -if (window.location.hash && performance.navigation.type !== 1) { - setTimeout(function() { - window.scrollTo(0, window.scrollY - 106); - }, 0); -} - -setTimeout(function() { - gumshoe.init({ // https://github.com/cferdinandi/gumshoe (scrollspy) - selector: '#spy-nav > ul > li > a', // Default link selector - selectorHeader: fixedHeaderSelector, // Fixed header selector - container: window, // The element to spy on scrolling in (must be a valid DOM Node) - offset: offset, // Distance in pixels to offset calculations - activeClass: 'active', // Class to apply to active navigation link and its parent list item - scrollDelay: false, // Wait until scrolling has stopped before updating the navigation - callback: function (nav) { - try { - window.history.replaceState({}, "", location.pathname + "#" + nav.target.id); - } catch(e) { /* Doesn't matter */ } - } - }); -}, 500); diff --git a/docs/js/_partials/tutorials.js b/docs/js/_partials/tutorials.js deleted file mode 100644 index 64db908a6..000000000 --- a/docs/js/_partials/tutorials.js +++ /dev/null @@ -1,10 +0,0 @@ - // trim blank lines around code -var codeBlocks = document.getElementsByTagName("code"); -for (var i = 0; i < codeBlocks.length; i++) { - codeBlocks[i].innerHTML = codeBlocks[i].innerHTML.trim(); -} - -// Calculate time to read -if (document.getElementById("timeToRead") !== null) { - function getText(a){for(var d="",c=0;c"; -} diff --git a/docs/js/_partials/vendor/clipboard.min.js b/docs/js/_partials/vendor/clipboard.min.js deleted file mode 100644 index 15210c824..000000000 --- a/docs/js/_partials/vendor/clipboard.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * clipboard.js v1.5.8 - * https://zenorocha.github.io/clipboard.js - * - * Licensed MIT © Zeno Rocha - */ -!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e;e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,e.Clipboard=t()}}(function(){var t,e,n;return function t(e,n,r){function o(a,s){if(!n[a]){if(!e[a]){var c="function"==typeof require&&require;if(!s&&c)return c(a,!0);if(i)return i(a,!0);var u=new Error("Cannot find module '"+a+"'");throw u.code="MODULE_NOT_FOUND",u}var l=n[a]={exports:{}};e[a][0].call(l.exports,function(t){var n=e[a][1][t];return o(n?n:t)},l,l.exports,t,e,n,r)}return n[a].exports}for(var i="function"==typeof require&&require,a=0;ar;r++)n[r].fn.apply(n[r].ctx,e);return this},off:function(t,e){var n=this.e||(this.e={}),r=n[t],o=[];if(r&&e)for(var i=0,a=r.length;a>i;i++)r[i].fn!==e&&r[i].fn._!==e&&o.push(r[i]);return o.length?n[t]=o:delete n[t],this}},e.exports=r},{}],8:[function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}n.__esModule=!0;var i=function(){function t(t,e){for(var n=0;n=0?n:0},p=function(t){var n=t.getBoundingClientRect();return n.top>=0&&n.left>=0&&n.bottom<=(e.innerHeight||document.documentElement.clientHeight)&&n.right<=(e.innerWidth||document.documentElement.clientWidth)},y=function(){u.sort((function(e,t){return e.distance>t.distance?-1:e.distance=o&&p(u[0].target))return C(u[0]),u[0];for(var r=0,a=u.length;re.length)break e;if(!(v instanceof a)){u.lastIndex=0;var b=u.exec(v),k=1;if(!b&&h&&m!=r.length-1){if(u.lastIndex=y,b=u.exec(e),!b)break;for(var w=b.index+(c?b[1].length:0),_=b.index+b[0].length,P=m,A=y,j=r.length;j>P&&_>A;++P)A+=r[P].length,w>=A&&(++m,y=A);if(r[m]instanceof a||r[P-1].greedy)continue;k=P-m,v=e.slice(y,A),b.index-=y}if(b){c&&(f=b[1].length);var w=b.index+f,b=b[0].slice(f),_=w+b.length,x=v.slice(0,w),O=v.slice(_),S=[m,k];x&&S.push(x);var N=new a(i,g?n.tokenize(b,g):b,d,b,h);S.push(N),O&&S.push(O),Array.prototype.splice.apply(r,S)}}}}}return r},hooks:{all:{},add:function(e,t){var a=n.hooks.all;a[e]=a[e]||[],a[e].push(t)},run:function(e,t){var a=n.hooks.all[e];if(a&&a.length)for(var r,l=0;r=a[l++];)r(t)}}},a=n.Token=function(e,t,n,a,r){this.type=e,this.content=t,this.alias=n,this.length=0|(a||"").length,this.greedy=!!r};if(a.stringify=function(e,t,r){if("string"==typeof e)return e;if("Array"===n.util.type(e))return e.map(function(n){return a.stringify(n,t,e)}).join("");var l={type:e.type,content:a.stringify(e.content,t,r),tag:"span",classes:["token",e.type],attributes:{},language:t,parent:r};if("comment"==l.type&&(l.attributes.spellcheck="true"),e.alias){var i="Array"===n.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(l.classes,i)}n.hooks.run("wrap",l);var o=Object.keys(l.attributes).map(function(e){return e+'="'+(l.attributes[e]||"").replace(/"/g,""")+'"'}).join(" ");return"<"+l.tag+' class="'+l.classes.join(" ")+'"'+(o?" "+o:"")+">"+l.content+""},!_self.document)return _self.addEventListener?(_self.addEventListener("message",function(e){var t=JSON.parse(e.data),a=t.language,r=t.code,l=t.immediateClose;_self.postMessage(n.highlight(r,n.languages[a],a)),l&&_self.close()},!1),_self.Prism):_self.Prism;var r=document.currentScript||[].slice.call(document.getElementsByTagName("script")).pop();return r&&(n.filename=r.src,!document.addEventListener||n.manual||r.hasAttribute("data-manual")||("loading"!==document.readyState?window.requestAnimationFrame?window.requestAnimationFrame(n.highlightAll):window.setTimeout(n.highlightAll,16):document.addEventListener("DOMContentLoaded",n.highlightAll))),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); -Prism.languages.markup={comment://,prolog:/<\?[\w\W]+?\?>/,doctype://i,cdata://i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\\1|\\?(?!\1)[\w\W])*\1|[^\s'">=]+))?)*\s*\/?>/i,inside:{tag:{pattern:/^<\/?[^\s>\/]+/i,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=(?:('|")[\w\W]*?(\1)|[^\s>]+)/i,inside:{punctuation:/[=>"']/}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:/&#?[\da-z]{1,8};/i},Prism.hooks.add("wrap",function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))}),Prism.languages.xml=Prism.languages.markup,Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup; -Prism.languages.css={comment:/\/\*[\w\W]*?\*\//,atrule:{pattern:/@[\w-]+?.*?(;|(?=\s*\{))/i,inside:{rule:/@[\w-]+/}},url:/url\((?:(["'])(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1|.*?)\)/i,selector:/[^\{\}\s][^\{\};]*?(?=\s*\{)/,string:{pattern:/("|')(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1/,greedy:!0},property:/(\b|\B)[\w-]+(?=\s*:)/i,important:/\B!important\b/i,"function":/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:]/},Prism.languages.css.atrule.inside.rest=Prism.util.clone(Prism.languages.css),Prism.languages.markup&&(Prism.languages.insertBefore("markup","tag",{style:{pattern:/()[\w\W]*?(?=<\/style>)/i,lookbehind:!0,inside:Prism.languages.css,alias:"language-css"}}),Prism.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/\s*style=("|').*?\1/i,inside:{"attr-name":{pattern:/^\s*style/i,inside:Prism.languages.markup.tag.inside},punctuation:/^\s*=\s*['"]|['"]\s*$/,"attr-value":{pattern:/.+/i,inside:Prism.languages.css}},alias:"language-css"}},Prism.languages.markup.tag)); -Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\w\W]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:{pattern:/(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,"boolean":/\b(true|false)\b/,"function":/[a-z0-9_]+(?=\()/i,number:/\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)\b/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,punctuation:/[{}[\];(),.:]/}; -Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield)\b/,number:/\b-?(0x[\dA-Fa-f]+|0b[01]+|0o[0-7]+|\d*\.?\d+([Ee][+-]?\d+)?|NaN|Infinity)\b/,"function":/[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*(?=\()/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*\*?|\/|~|\^|%|\.{3}/}),Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^\/])\/(?!\/)(\[.+?]|\\.|[^\/\\\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})]))/,lookbehind:!0,greedy:!0}}),Prism.languages.insertBefore("javascript","string",{"template-string":{pattern:/`(?:\\\\|\\?[^\\])*?`/,greedy:!0,inside:{interpolation:{pattern:/\$\{[^}]+\}/,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}}}),Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/()[\w\W]*?(?=<\/script>)/i,lookbehind:!0,inside:Prism.languages.javascript,alias:"language-javascript"}}),Prism.languages.js=Prism.languages.javascript; -!function(e){var t={variable:[{pattern:/\$?\(\([\w\W]+?\)\)/,inside:{variable:[{pattern:/(^\$\(\([\w\W]+)\)\)/,lookbehind:!0},/^\$\(\(/],number:/\b-?(?:0x[\dA-Fa-f]+|\d*\.?\d+(?:[Ee]-?\d+)?)\b/,operator:/--?|-=|\+\+?|\+=|!=?|~|\*\*?|\*=|\/=?|%=?|<<=?|>>=?|<=?|>=?|==?|&&?|&=|\^=?|\|\|?|\|=|\?|:/,punctuation:/\(\(?|\)\)?|,|;/}},{pattern:/\$\([^)]+\)|`[^`]+`/,inside:{variable:/^\$\(|^`|\)$|`$/}},/\$(?:[a-z0-9_#\?\*!@]+|\{[^}]+\})/i]};e.languages.bash={shebang:{pattern:/^#!\s*\/bin\/bash|^#!\s*\/bin\/sh/,alias:"important"},comment:{pattern:/(^|[^"{\\])#.*/,lookbehind:!0},string:[{pattern:/((?:^|[^<])<<\s*)(?:"|')?(\w+?)(?:"|')?\s*\r?\n(?:[\s\S])*?\r?\n\2/g,lookbehind:!0,greedy:!0,inside:t},{pattern:/(["'])(?:\\\\|\\?[^\\])*?\1/g,greedy:!0,inside:t}],variable:t.variable,"function":{pattern:/(^|\s|;|\||&)(?:alias|apropos|apt-get|aptitude|aspell|awk|basename|bash|bc|bg|builtin|bzip2|cal|cat|cd|cfdisk|chgrp|chmod|chown|chroot|chkconfig|cksum|clear|cmp|comm|command|cp|cron|crontab|csplit|cut|date|dc|dd|ddrescue|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|du|egrep|eject|enable|env|ethtool|eval|exec|expand|expect|export|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|getopts|git|grep|groupadd|groupdel|groupmod|groups|gzip|hash|head|help|hg|history|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|jobs|join|kill|killall|less|link|ln|locate|logname|logout|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|make|man|mkdir|mkfifo|mkisofs|mknod|more|most|mount|mtools|mtr|mv|mmv|nano|netstat|nice|nl|nohup|notify-send|npm|nslookup|open|op|passwd|paste|pathchk|ping|pkill|popd|pr|printcap|printenv|printf|ps|pushd|pv|pwd|quota|quotacheck|quotactl|ram|rar|rcp|read|readarray|readonly|reboot|rename|renice|remsync|rev|rm|rmdir|rsync|screen|scp|sdiff|sed|seq|service|sftp|shift|shopt|shutdown|sleep|slocate|sort|source|split|ssh|stat|strace|su|sudo|sum|suspend|sync|tail|tar|tee|test|time|timeout|times|touch|top|traceroute|trap|tr|tsort|tty|type|ulimit|umask|umount|unalias|uname|unexpand|uniq|units|unrar|unshar|uptime|useradd|userdel|usermod|users|uuencode|uudecode|v|vdir|vi|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yes|zip)(?=$|\s|;|\||&)/,lookbehind:!0},keyword:{pattern:/(^|\s|;|\||&)(?:let|:|\.|if|then|else|elif|fi|for|break|continue|while|in|case|function|select|do|done|until|echo|exit|return|set|declare)(?=$|\s|;|\||&)/,lookbehind:!0},"boolean":{pattern:/(^|\s|;|\||&)(?:true|false)(?=$|\s|;|\||&)/,lookbehind:!0},operator:/&&?|\|\|?|==?|!=?|<<>|<=?|>=?|=~/,punctuation:/\$?\(\(?|\)\)?|\.\.|[{}[\];]/};var a=t.variable[1].inside;a["function"]=e.languages.bash["function"],a.keyword=e.languages.bash.keyword,a.boolean=e.languages.bash.boolean,a.operator=e.languages.bash.operator,a.punctuation=e.languages.bash.punctuation}(Prism); -!function(e){e.languages.ruby=e.languages.extend("clike",{comment:/#(?!\{[^\r\n]*?\}).*/,keyword:/\b(alias|and|BEGIN|begin|break|case|class|def|define_method|defined|do|each|else|elsif|END|end|ensure|false|for|if|in|module|new|next|nil|not|or|raise|redo|require|rescue|retry|return|self|super|then|throw|true|undef|unless|until|when|while|yield)\b/});var n={pattern:/#\{[^}]+\}/,inside:{delimiter:{pattern:/^#\{|\}$/,alias:"tag"},rest:e.util.clone(e.languages.ruby)}};e.languages.insertBefore("ruby","keyword",{regex:[{pattern:/%r([^a-zA-Z0-9\s\{\(\[<])(?:[^\\]|\\[\s\S])*?\1[gim]{0,3}/,greedy:!0,inside:{interpolation:n}},{pattern:/%r\((?:[^()\\]|\\[\s\S])*\)[gim]{0,3}/,greedy:!0,inside:{interpolation:n}},{pattern:/%r\{(?:[^#{}\\]|#(?:\{[^}]+\})?|\\[\s\S])*\}[gim]{0,3}/,greedy:!0,inside:{interpolation:n}},{pattern:/%r\[(?:[^\[\]\\]|\\[\s\S])*\][gim]{0,3}/,greedy:!0,inside:{interpolation:n}},{pattern:/%r<(?:[^<>\\]|\\[\s\S])*>[gim]{0,3}/,greedy:!0,inside:{interpolation:n}},{pattern:/(^|[^\/])\/(?!\/)(\[.+?]|\\.|[^\/\\\r\n])+\/[gim]{0,3}(?=\s*($|[\r\n,.;})]))/,lookbehind:!0,greedy:!0}],variable:/[@$]+[a-zA-Z_][a-zA-Z_0-9]*(?:[?!]|\b)/,symbol:/:[a-zA-Z_][a-zA-Z_0-9]*(?:[?!]|\b)/}),e.languages.insertBefore("ruby","number",{builtin:/\b(Array|Bignum|Binding|Class|Continuation|Dir|Exception|FalseClass|File|Stat|File|Fixnum|Float|Hash|Integer|IO|MatchData|Method|Module|NilClass|Numeric|Object|Proc|Range|Regexp|String|Struct|TMS|Symbol|ThreadGroup|Thread|Time|TrueClass)\b/,constant:/\b[A-Z][a-zA-Z_0-9]*(?:[?!]|\b)/}),e.languages.ruby.string=[{pattern:/%[qQiIwWxs]?([^a-zA-Z0-9\s\{\(\[<])(?:[^\\]|\\[\s\S])*?\1/,greedy:!0,inside:{interpolation:n}},{pattern:/%[qQiIwWxs]?\((?:[^()\\]|\\[\s\S])*\)/,greedy:!0,inside:{interpolation:n}},{pattern:/%[qQiIwWxs]?\{(?:[^#{}\\]|#(?:\{[^}]+\})?|\\[\s\S])*\}/,greedy:!0,inside:{interpolation:n}},{pattern:/%[qQiIwWxs]?\[(?:[^\[\]\\]|\\[\s\S])*\]/,greedy:!0,inside:{interpolation:n}},{pattern:/%[qQiIwWxs]?<(?:[^<>\\]|\\[\s\S])*>/,greedy:!0,inside:{interpolation:n}},{pattern:/("|')(#\{[^}]+\}|\\(?:\r?\n|\r)|\\?.)*?\1/,greedy:!0,inside:{interpolation:n}}]}(Prism); -Prism.languages.java=Prism.languages.extend("clike",{keyword:/\b(abstract|continue|for|new|switch|assert|default|goto|package|synchronized|boolean|do|if|private|this|break|double|implements|protected|throw|byte|else|import|public|throws|case|enum|instanceof|return|transient|catch|extends|int|short|try|char|final|interface|static|void|class|finally|long|strictfp|volatile|const|float|native|super|while)\b/,number:/\b0b[01]+\b|\b0x[\da-f]*\.?[\da-fp\-]+\b|\b\d*\.?\d+(?:e[+-]?\d+)?[df]?\b/i,operator:{pattern:/(^|[^.])(?:\+[+=]?|-[-=]?|!=?|<>?>?=?|==?|&[&=]?|\|[|=]?|\*=?|\/=?|%=?|\^=?|[?:~])/m,lookbehind:!0}}),Prism.languages.insertBefore("java","function",{annotation:{alias:"punctuation",pattern:/(^|[^.])@\w+/,lookbehind:!0}}); -!function(n){n.languages.kotlin=n.languages.extend("clike",{keyword:{pattern:/(^|[^.])\b(?:abstract|annotation|as|break|by|catch|class|companion|const|constructor|continue|crossinline|data|do|else|enum|final|finally|for|fun|get|if|import|in|init|inline|inner|interface|internal|is|lateinit|noinline|null|object|open|out|override|package|private|protected|public|reified|return|sealed|set|super|tailrec|this|throw|to|try|val|var|when|where|while)\b/,lookbehind:!0},"function":[/\w+(?=\s*\()/,{pattern:/(\.)\w+(?=\s*\{)/,lookbehind:!0}],number:/\b(?:0[bx][\da-fA-F]+|\d+(?:\.\d+)?(?:e[+-]?\d+)?[fFL]?)\b/,operator:/\+[+=]?|-[-=>]?|==?=?|!(?:!|==?)?|[\/*%<>]=?|[?:]:?|\.\.|&&|\|\||\b(?:and|inv|or|shl|shr|ushr|xor)\b/}),delete n.languages.kotlin["class-name"],n.languages.insertBefore("kotlin","string",{"raw-string":{pattern:/(["'])\1\1[\s\S]*?\1{3}/,alias:"string"}}),n.languages.insertBefore("kotlin","keyword",{annotation:{pattern:/\B@(?:\w+:)?(?:[A-Z]\w*|\[[^\]]+\])/,alias:"builtin"}}),n.languages.insertBefore("kotlin","function",{label:{pattern:/\w+@|@\w+/,alias:"symbol"}});var e=[{pattern:/\$\{[^}]+\}/,inside:{delimiter:{pattern:/^\$\{|\}$/,alias:"variable"},rest:n.util.clone(n.languages.kotlin)}},{pattern:/\$\w+/,alias:"variable"}];n.languages.kotlin.string.inside=n.languages.kotlin["raw-string"].inside={interpolation:e}}(Prism); -Prism.languages.sql={comment:{pattern:/(^|[^\\])(?:\/\*[\w\W]*?\*\/|(?:--|\/\/|#).*)/,lookbehind:!0},string:{pattern:/(^|[^@\\])("|')(?:\\?[\s\S])*?\2/,greedy:!0,lookbehind:!0},variable:/@[\w.$]+|@("|'|`)(?:\\?[\s\S])+?\1/,"function":/\b(?:COUNT|SUM|AVG|MIN|MAX|FIRST|LAST|UCASE|LCASE|MID|LEN|ROUND|NOW|FORMAT)(?=\s*\()/i,keyword:/\b(?:ACTION|ADD|AFTER|ALGORITHM|ALL|ALTER|ANALYZE|ANY|APPLY|AS|ASC|AUTHORIZATION|AUTO_INCREMENT|BACKUP|BDB|BEGIN|BERKELEYDB|BIGINT|BINARY|BIT|BLOB|BOOL|BOOLEAN|BREAK|BROWSE|BTREE|BULK|BY|CALL|CASCADED?|CASE|CHAIN|CHAR VARYING|CHARACTER (?:SET|VARYING)|CHARSET|CHECK|CHECKPOINT|CLOSE|CLUSTERED|COALESCE|COLLATE|COLUMN|COLUMNS|COMMENT|COMMIT|COMMITTED|COMPUTE|CONNECT|CONSISTENT|CONSTRAINT|CONTAINS|CONTAINSTABLE|CONTINUE|CONVERT|CREATE|CROSS|CURRENT(?:_DATE|_TIME|_TIMESTAMP|_USER)?|CURSOR|DATA(?:BASES?)?|DATE(?:TIME)?|DBCC|DEALLOCATE|DEC|DECIMAL|DECLARE|DEFAULT|DEFINER|DELAYED|DELETE|DELIMITER(?:S)?|DENY|DESC|DESCRIBE|DETERMINISTIC|DISABLE|DISCARD|DISK|DISTINCT|DISTINCTROW|DISTRIBUTED|DO|DOUBLE(?: PRECISION)?|DROP|DUMMY|DUMP(?:FILE)?|DUPLICATE KEY|ELSE|ENABLE|ENCLOSED BY|END|ENGINE|ENUM|ERRLVL|ERRORS|ESCAPE(?:D BY)?|EXCEPT|EXEC(?:UTE)?|EXISTS|EXIT|EXPLAIN|EXTENDED|FETCH|FIELDS|FILE|FILLFACTOR|FIRST|FIXED|FLOAT|FOLLOWING|FOR(?: EACH ROW)?|FORCE|FOREIGN|FREETEXT(?:TABLE)?|FROM|FULL|FUNCTION|GEOMETRY(?:COLLECTION)?|GLOBAL|GOTO|GRANT|GROUP|HANDLER|HASH|HAVING|HOLDLOCK|IDENTITY(?:_INSERT|COL)?|IF|IGNORE|IMPORT|INDEX|INFILE|INNER|INNODB|INOUT|INSERT|INT|INTEGER|INTERSECT|INTO|INVOKER|ISOLATION LEVEL|JOIN|KEYS?|KILL|LANGUAGE SQL|LAST|LEFT|LIMIT|LINENO|LINES|LINESTRING|LOAD|LOCAL|LOCK|LONG(?:BLOB|TEXT)|MATCH(?:ED)?|MEDIUM(?:BLOB|INT|TEXT)|MERGE|MIDDLEINT|MODIFIES SQL DATA|MODIFY|MULTI(?:LINESTRING|POINT|POLYGON)|NATIONAL(?: CHAR VARYING| CHARACTER(?: VARYING)?| VARCHAR)?|NATURAL|NCHAR(?: VARCHAR)?|NEXT|NO(?: SQL|CHECK|CYCLE)?|NONCLUSTERED|NULLIF|NUMERIC|OFF?|OFFSETS?|ON|OPEN(?:DATASOURCE|QUERY|ROWSET)?|OPTIMIZE|OPTION(?:ALLY)?|ORDER|OUT(?:ER|FILE)?|OVER|PARTIAL|PARTITION|PERCENT|PIVOT|PLAN|POINT|POLYGON|PRECEDING|PRECISION|PREV|PRIMARY|PRINT|PRIVILEGES|PROC(?:EDURE)?|PUBLIC|PURGE|QUICK|RAISERROR|READ(?:S SQL DATA|TEXT)?|REAL|RECONFIGURE|REFERENCES|RELEASE|RENAME|REPEATABLE|REPLICATION|REQUIRE|RESTORE|RESTRICT|RETURNS?|REVOKE|RIGHT|ROLLBACK|ROUTINE|ROW(?:COUNT|GUIDCOL|S)?|RTREE|RULE|SAVE(?:POINT)?|SCHEMA|SELECT|SERIAL(?:IZABLE)?|SESSION(?:_USER)?|SET(?:USER)?|SHARE MODE|SHOW|SHUTDOWN|SIMPLE|SMALLINT|SNAPSHOT|SOME|SONAME|START(?:ING BY)?|STATISTICS|STATUS|STRIPED|SYSTEM_USER|TABLES?|TABLESPACE|TEMP(?:ORARY|TABLE)?|TERMINATED BY|TEXT(?:SIZE)?|THEN|TIMESTAMP|TINY(?:BLOB|INT|TEXT)|TOP?|TRAN(?:SACTIONS?)?|TRIGGER|TRUNCATE|TSEQUAL|TYPES?|UNBOUNDED|UNCOMMITTED|UNDEFINED|UNION|UNIQUE|UNPIVOT|UPDATE(?:TEXT)?|USAGE|USE|USER|USING|VALUES?|VAR(?:BINARY|CHAR|CHARACTER|YING)|VIEW|WAITFOR|WARNINGS|WHEN|WHERE|WHILE|WITH(?: ROLLUP|IN)?|WORK|WRITE(?:TEXT)?)\b/i,"boolean":/\b(?:TRUE|FALSE|NULL)\b/i,number:/\b-?(?:0x)?\d*\.?[\da-f]+\b/,operator:/[-+*\/=%^~]|&&?|\|?\||!=?|<(?:=>?|<|>)?|>[>=]?|\b(?:AND|BETWEEN|IN|LIKE|NOT|OR|IS|DIV|REGEXP|RLIKE|SOUNDS LIKE|XOR)\b/i,punctuation:/[;[\]()`,.]/}; -!function(){if("undefined"!=typeof self&&self.Prism&&self.document){var t=[],e={},n=function(){};Prism.plugins.toolbar={};var a=Prism.plugins.toolbar.registerButton=function(n,a){var o;o="function"==typeof a?a:function(t){var e;return"function"==typeof a.onClick?(e=document.createElement("button"),e.type="button",e.addEventListener("click",function(){a.onClick.call(this,t)})):"string"==typeof a.url?(e=document.createElement("a"),e.href=a.url):e=document.createElement("span"),e.textContent=a.text,e},t.push(e[n]=o)},o=Prism.plugins.toolbar.hook=function(a){var o=a.element.parentNode;if(o&&/pre/i.test(o.nodeName)&&!o.classList.contains("code-toolbar")){o.classList.add("code-toolbar");var r=document.createElement("div");r.classList.add("toolbar"),document.body.hasAttribute("data-toolbar-order")&&(t=document.body.getAttribute("data-toolbar-order").split(",").map(function(t){return e[t]||n})),t.forEach(function(t){var e=t(a);if(e){var n=document.createElement("div");n.classList.add("toolbar-item"),n.appendChild(e),r.appendChild(n)}}),o.appendChild(r)}};a("label",function(t){var e=t.element.parentNode;if(e&&/pre/i.test(e.nodeName)&&e.hasAttribute("data-label")){var n,a,o=e.getAttribute("data-label");try{a=document.querySelector("template#"+o)}catch(r){}return a?n=a.content:(e.hasAttribute("data-url")?(n=document.createElement("a"),n.href=e.getAttribute("data-url")):n=document.createElement("span"),n.textContent=o),n}}),Prism.hooks.add("complete",o)}}(); -!function(){if("undefined"!=typeof self&&self.Prism&&self.document){if(!Prism.plugins.toolbar)return console.warn("Copy to Clipboard plugin loaded before Toolbar plugin."),void 0;var o=window.Clipboard||void 0;o||"function"!=typeof require||(o=require("clipboard"));var e=[];if(!o){var t=document.createElement("script"),n=document.querySelector("head");t.onload=function(){if(o=window.Clipboard)for(;e.length;)e.pop()()},t.src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/1.5.8/clipboard.min.js",n.appendChild(t)}Prism.plugins.toolbar.registerButton("copy-to-clipboard",function(t){function n(){var e=new o(i,{text:function(){return t.code}});e.on("success",function(){i.textContent="Copied!",r()}),e.on("error",function(){i.textContent="Press Ctrl+C to copy",r()})}function r(){setTimeout(function(){i.textContent="Copy"},5e3)}var i=document.createElement("a");return i.textContent="Copy",o?n():e.push(n),i})}}(); diff --git a/docs/js/_partials/vendor/smoothscroll.min.js b/docs/js/_partials/vendor/smoothscroll.min.js deleted file mode 100644 index 037825077..000000000 --- a/docs/js/_partials/vendor/smoothscroll.min.js +++ /dev/null @@ -1,3 +0,0 @@ -// https://github.com/cferdinandi/smooth-scroll -/*! smooth-scroll v10.2.1 | (c) 2016 Chris Ferdinandi | MIT License | http://github.com/cferdinandi/smooth-scroll */ -!(function(e,t){"function"==typeof define&&define.amd?define([],t(e)):"object"==typeof exports?module.exports=t(e):e.smoothScroll=t(e)})("undefined"!=typeof global?global:this.window||this.global,(function(e){"use strict";var t,n,o,r,a,c,l,i={},u="querySelector"in document&&"addEventListener"in e,s={selector:"[data-scroll]",selectorHeader:null,speed:500,easing:"easeInOutCubic",offset:0,callback:function(){}},d=function(){var e={},t=!1,n=0,o=arguments.length;"[object Boolean]"===Object.prototype.toString.call(arguments[0])&&(t=arguments[0],n++);for(var r=function(n){for(var o in n)Object.prototype.hasOwnProperty.call(n,o)&&(t&&"[object Object]"===Object.prototype.toString.call(n[o])?e[o]=d(!0,e[o],n[o]):e[o]=n[o])};n=0&&t.item(n)!==this;);return n>-1});e&&e!==document;e=e.parentNode)if(e.matches(t))return e;return null},m=function(e){"#"===e.charAt(0)&&(e=e.substr(1));for(var t,n=String(e),o=n.length,r=-1,a="",c=n.charCodeAt(0);++r=1&&t<=31||127==t||0===r&&t>=48&&t<=57||1===r&&t>=48&&t<=57&&45===c?"\\"+t.toString(16)+" ":t>=128||45===t||95===t||t>=48&&t<=57||t>=65&&t<=90||t>=97&&t<=122?n.charAt(r):"\\"+n.charAt(r)}return"#"+a},p=function(e,t){var n;return"easeInQuad"===e&&(n=t*t),"easeOutQuad"===e&&(n=t*(2-t)),"easeInOutQuad"===e&&(n=t<.5?2*t*t:-1+(4-2*t)*t),"easeInCubic"===e&&(n=t*t*t),"easeOutCubic"===e&&(n=--t*t*t+1),"easeInOutCubic"===e&&(n=t<.5?4*t*t*t:(t-1)*(2*t-2)*(2*t-2)+1),"easeInQuart"===e&&(n=t*t*t*t),"easeOutQuart"===e&&(n=1- --t*t*t*t),"easeInOutQuart"===e&&(n=t<.5?8*t*t*t*t:1-8*--t*t*t*t),"easeInQuint"===e&&(n=t*t*t*t*t),"easeOutQuint"===e&&(n=1+--t*t*t*t*t),"easeInOutQuint"===e&&(n=t<.5?16*t*t*t*t*t:1+16*--t*t*t*t*t),n||t},g=function(e,t,n){var o=0;if(e.offsetParent)do o+=e.offsetTop,e=e.offsetParent;while(e);return o=Math.max(o-t-n,0),Math.min(o,v()-b())},b=function(){return Math.max(document.documentElement.clientHeight,e.innerHeight||0)},v=function(){return Math.max(document.body.scrollHeight,document.documentElement.scrollHeight,document.body.offsetHeight,document.documentElement.offsetHeight,document.body.clientHeight,document.documentElement.clientHeight)},y=function(e){return e&&"object"==typeof JSON&&"function"==typeof JSON.parse?JSON.parse(e):{}},O=function(e){return e?f(e)+e.offsetTop:0},S=function(t,n,o){o||(t.focus(),document.activeElement.id!==t.id&&(t.setAttribute("tabindex","-1"),t.focus(),t.style.outline="none"),e.scrollTo(0,n))};i.animateScroll=function(n,o,c){var i=y(o?o.getAttribute("data-options"):null),u=d(t||s,c||{},i),f="[object Number]"===Object.prototype.toString.call(n),h=f||!n.tagName?null:n;if(f||h){var m=e.pageYOffset;u.selectorHeader&&!r&&(r=document.querySelector(u.selectorHeader)),a||(a=O(r));var b,E,I=f?n:g(h,a,parseInt(u.offset,10)),H=I-m,A=v(),j=0,C=function(t,r,a){var c=e.pageYOffset;(t==r||c==r||e.innerHeight+c>=A)&&(clearInterval(a),S(n,r,f),u.callback(n,o))},M=function(){j+=16,b=j/parseInt(u.speed,10),b=b>1?1:b,E=m+H*p(u.easing,b),e.scrollTo(0,Math.floor(E)),C(E,I,l)},w=function(){clearInterval(l),l=setInterval(M,16)};0===e.pageYOffset&&e.scrollTo(0,0),w()}};var E=function(t){var r;try{r=m(decodeURIComponent(e.location.hash))}catch(t){r=m(e.location.hash)}n&&(n.id=n.getAttribute("data-scroll-id"),i.animateScroll(n,o),n=null,o=null)},I=function(r){if(0===r.button&&!r.metaKey&&!r.ctrlKey&&(o=h(r.target,t.selector),o&&"a"===o.tagName.toLowerCase()&&o.hostname===e.location.hostname&&o.pathname===e.location.pathname&&/#/.test(o.href))){var a;try{a=m(decodeURIComponent(o.hash))}catch(e){a=m(o.hash)}if("#"===a){r.preventDefault(),n=document.body;var c=n.id?n.id:"smooth-scroll-top";return n.setAttribute("data-scroll-id",c),n.id="",void(e.location.hash.substring(1)===c?E():e.location.hash=c)}n=document.querySelector(a),n&&(n.setAttribute("data-scroll-id",n.id),n.id="",o.hash===e.location.hash&&(r.preventDefault(),E()))}},H=function(e){c||(c=setTimeout((function(){c=null,a=O(r)}),66))};return i.destroy=function(){t&&(document.removeEventListener("click",I,!1),e.removeEventListener("resize",H,!1),t=null,n=null,o=null,r=null,a=null,c=null,l=null)},i.init=function(n){u&&(i.destroy(),t=d(s,n||{}),r=t.selectorHeader?document.querySelector(t.selectorHeader):null,a=O(r),document.addEventListener("click",I,!1),e.addEventListener("hashchange",E,!1),r&&e.addEventListener("resize",H,!1))},i})); diff --git a/docs/js/scripts.js b/docs/js/scripts.js deleted file mode 100644 index 28c82cd03..000000000 --- a/docs/js/scripts.js +++ /dev/null @@ -1,9 +0,0 @@ ---- ---- - -{% include_relative _partials/vendor/clipboard.min.js %} -{% include_relative _partials/vendor/prism.min.js %} -{% include_relative _partials/vendor/smoothscroll.min.js %} -{% include_relative _partials/vendor/gumshoe.min.js %} -{% include_relative _partials/tutorials.js %} -{% include_relative _partials/main.js %} 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..85fc9904a --- /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(new Proxy("https://proxy")) + .setDefaultHeader("Accept", "application/json") + .followRedirects(false) + .enableCookieManagement(false) + .addInterceptor(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/object-mapper-gson/.gitignore b/object-mapper-gson/.gitignore deleted file mode 100644 index 7baad6ab7..000000000 --- a/object-mapper-gson/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -.classpath -pom.xml.releaseBackup -.project -target -.DS_Store -.settings -release.properties -META-INF -.idea -*.iml -.gitconfig - diff --git a/object-mapper-gson/.travis.yml b/object-mapper-gson/.travis.yml deleted file mode 100644 index d34d49296..000000000 --- a/object-mapper-gson/.travis.yml +++ /dev/null @@ -1,12 +0,0 @@ -language: java - -jdk: - - oraclejdk8 - -os: - - linux - -cache: - directories: - - .autoconf - - $HOME/.m2 diff --git a/object-mapper-gson/CHANGELOG.md b/object-mapper-gson/CHANGELOG.md deleted file mode 100644 index 6476f80d3..000000000 --- a/object-mapper-gson/CHANGELOG.md +++ /dev/null @@ -1,2 +0,0 @@ -## 1.0.00 -* init release \ No newline at end of file diff --git a/object-mapper-gson/LICENSE b/object-mapper-gson/LICENSE deleted file mode 100644 index 16dc2225b..000000000 --- a/object-mapper-gson/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -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. diff --git a/object-mapper-gson/checkstyle.xml b/object-mapper-gson/checkstyle.xml deleted file mode 100644 index 6fcfd982d..000000000 --- a/object-mapper-gson/checkstyle.xml +++ /dev/null @@ -1,240 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/object-mapper-gson/deploy.sh b/object-mapper-gson/deploy.sh deleted file mode 100755 index 843fa9fa6..000000000 --- a/object-mapper-gson/deploy.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/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 -mvn release:prepare -DskipTests -mvn release:perform -DskipTests diff --git a/object-mapper-gson/suppressions.xml b/object-mapper-gson/suppressions.xml deleted file mode 100644 index cb3791156..000000000 --- a/object-mapper-gson/suppressions.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/object-mapper-jackson/.travis.yml b/object-mapper-jackson/.travis.yml deleted file mode 100644 index d34d49296..000000000 --- a/object-mapper-jackson/.travis.yml +++ /dev/null @@ -1,12 +0,0 @@ -language: java - -jdk: - - oraclejdk8 - -os: - - linux - -cache: - directories: - - .autoconf - - $HOME/.m2 diff --git a/object-mapper-jackson/CHANGELOG.md b/object-mapper-jackson/CHANGELOG.md deleted file mode 100644 index 9bb919baf..000000000 --- a/object-mapper-jackson/CHANGELOG.md +++ /dev/null @@ -1,2 +0,0 @@ -## 2.0.00 -* init release \ No newline at end of file diff --git a/object-mapper-jackson/checkstyle.xml b/object-mapper-jackson/checkstyle.xml deleted file mode 100644 index 6fcfd982d..000000000 --- a/object-mapper-jackson/checkstyle.xml +++ /dev/null @@ -1,240 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/object-mapper-jackson/suppressions.xml b/object-mapper-jackson/suppressions.xml deleted file mode 100644 index cb3791156..000000000 --- a/object-mapper-jackson/suppressions.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/pom.xml b/pom.xml index a892ead51..e0badc8ab 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.konghq unirest-java-parent pom - 2.3.08-SNAPSHOT + 4.8.2-SNAPSHOT unirest Parent pom for unirest packages http://github.com/Kong/unirest-java/ @@ -19,16 +19,25 @@ unirest - object-mapper-gson - object-mapper-jackson + unirest-modules-gson + unirest-modules-jackson + unirest-modules-jackson-legacy + unirest-modules-mocks + unirest-bdd-tests + unirest-java-bom UTF-8 UTF-8 - 2.9.9 + 3.1.1 ${project.basedir} + 6.0.3 + 5.20.0 + 3.6.0 + 3.27.7 + 3.5.4 @@ -95,7 +104,16 @@
${main.dir}/build/fileHeader.txt
true + **/*.scss + **/*.b64 + **/*.svg + **/Gemfile + .java-version + .java-version **/*.js + **/*.json + **/*.p12 + **/*.pem **/*.html **/*.xml **/*.txt @@ -104,8 +122,11 @@ **/*.yml .idea/** .github/** + **/*.scssc + **/.jekyll-metadata .git* LICENSE + Gemfile.lock @@ -133,12 +154,12 @@ org.apache.maven.plugins maven-checkstyle-plugin - 2.17 + ${maven-checkstyle-plugin.version} com.puppycrawl.tools checkstyle - 8.18 + 10.26.1 @@ -159,10 +180,23 @@ 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.1 + 0.8.12 default-prepare-agent @@ -201,41 +235,50 @@ org.apache.maven.plugins maven-release-plugin - 2.5 + 3.0.0 - maven.test.skip=true v@{project.version} - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.7 + org.sonatype.central + central-publishing-maven-plugin + 0.8.0 true - ossrh - https://oss.sonatype.org/ - true + central + true + + + + + + + + + + + org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.13.0 true 128m 512m - 1.8 - 1.8 + 11 + 11 UTF-8 org.apache.maven.plugins maven-source-plugin - 2.2.1 + 3.3.1 attach-sources @@ -248,7 +291,10 @@ org.apache.maven.plugins maven-javadoc-plugin - 2.9.1 + 3.8.0 + + 11 + attach-javadocs @@ -258,100 +304,7 @@ - - org.apache.maven.plugins - maven-enforcer-plugin - 1.4.1 - - - org.sonatype.ossindex.maven - ossindex-maven-enforcer-rules - 3.0.2 - - - - - vulnerability-checks - validate - - enforce - - - - - - - - - enforce-versions - - enforce - - - - - - [3.2,) - - - 1.8 - - - - - - - - - - - - org.apache.maven.plugins - maven-shade-plugin - 3.2.1 - - - package - - shade - - - - true - - - standalone - - - - - org.apache.http - unirest.shaded.org.apache.http - - - - com.fasterxml.jackson - unirest.shaded.com.fasterxml.jackson - - - - com.google.common - unirest.shaded.com.google.common - - - - - true - - - - - - - @@ -360,19 +313,58 @@ org.mockito mockito-core - 2.23.4 + ${mockito.version} + test + + + org.mockito + mockito-junit-jupiter + ${mockito.version} test + - junit - junit - 4.12 + 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.0 + 1.5.3 + test + + + + com.google.guava + guava + 33.5.0-jre test @@ -382,7 +374,7 @@ org.apache.maven.plugins maven-checkstyle-plugin - 2.17 + ${maven-checkstyle-plugin.version} @@ -394,4 +386,4 @@ - \ No newline at end of file + diff --git a/unirest-bdd-tests/pom.xml b/unirest-bdd-tests/pom.xml new file mode 100644 index 000000000..002683fc9 --- /dev/null +++ b/unirest-bdd-tests/pom.xml @@ -0,0 +1,58 @@ + + + + unirest-java-parent + com.konghq + 4.8.2-SNAPSHOT + + 4.0.0 + + unirest-bdd-tests + unirest-bdd-tests + + + 11 + 11 + ${project.parent.basedir} + + + + + com.konghq + unirest-java-core + ${project.version} + test + + + com.konghq + unirest-modules-gson + ${project.version} + test + + + io.javalin + javalin + 6.7.0 + 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 + + + + 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/src/test/java/BehaviorTests/AsEmptyTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/AsEmptyTest.java similarity index 53% rename from unirest/src/test/java/BehaviorTests/AsEmptyTest.java rename to unirest-bdd-tests/src/test/java/BehaviorTests/AsEmptyTest.java index d94e399e3..4b966a5ab 100644 --- a/unirest/src/test/java/BehaviorTests/AsEmptyTest.java +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/AsEmptyTest.java @@ -25,43 +25,55 @@ package BehaviorTests; -import org.junit.Test; -import kong.unirest.HttpResponse; -import kong.unirest.Unirest; +import kong.unirest.core.Empty; +import kong.unirest.core.HttpResponse; +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; -import static junit.framework.TestCase.assertEquals; +import static org.junit.jupiter.api.Assertions.*; -public class AsEmptyTest extends BddTest { + +class AsEmptyTest extends BddTest { + + @Test + void asEmptyParams() { + Unirest.get(MockServer.ERROR_RESPONSE) + .asEmpty() + .ifFailure(response -> { + assertEquals(400, response.getStatus()); + }) + .ifSuccess(s -> fail("fail")); + } @Test - public void canDoAEmptyRequestThatDoesNotParseBodyAtAll() { - MockServer.addResponseHeader("foo", "bar"); - HttpResponse res = Unirest.get(MockServer.GET).asEmpty(); + void canDoAEmptyRequestThatDoesNotParseBodyAtAll() { + MockServer.addResponseHeader("Content-Type", "json"); + var res = Unirest.get(MockServer.GET).asEmpty(); + assertNull(res.getBody()); assertEquals(200, res.getStatus()); - assertEquals(null, res.getBody()); - assertEquals("bar", res.getHeaders().getFirst("foo")); + assertEquals("json;charset=utf-8", res.getHeaders().getFirst("Content-Type")); } @Test - public void canDoEmptyAsync() throws Exception { - MockServer.addResponseHeader("foo", "bar"); - HttpResponse res = Unirest.get(MockServer.GET).asEmptyAsync().get(); + 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(null, res.getBody()); - assertEquals("bar", res.getHeaders().getFirst("foo")); + assertEquals("json;charset=utf-8", res.getHeaders().getFirst("Content-Type")); } @Test - public void canDoEmptyAsyncWithCallback() { - MockServer.addResponseHeader("foo", "bar"); + void canDoEmptyAsyncWithCallback() { + MockServer.addResponseHeader("Content-Type", "json;charset=utf-8"); Unirest.get(MockServer.GET) .asEmptyAsync(res -> { + assertNull(res.getBody()); assertEquals(200, res.getStatus()); - assertEquals(null, res.getBody()); - assertEquals("bar", res.getHeaders().getFirst("foo")); + assertEquals("json;charset=utf-8", res.getHeaders().getFirst("Content-Type")); asyncSuccess(); }); diff --git a/unirest/src/test/java/BehaviorTests/AsFileTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/AsFileTest.java similarity index 53% rename from unirest/src/test/java/BehaviorTests/AsFileTest.java rename to unirest-bdd-tests/src/test/java/BehaviorTests/AsFileTest.java index e66557f44..7699520d5 100644 --- a/unirest/src/test/java/BehaviorTests/AsFileTest.java +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/AsFileTest.java @@ -25,34 +25,37 @@ package BehaviorTests; -import org.junit.Test; -import kong.unirest.JacksonObjectMapper; -import kong.unirest.TestUtil; -import kong.unirest.Unirest; +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.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; -public class AsFileTest extends BddTest { +class AsFileTest extends BddTest { - private Path test = Paths.get("results.json"); - private JacksonObjectMapper om = new JacksonObjectMapper(); + private final Path test = Paths.get("results.json"); + private final JacksonObjectMapper om = new JacksonObjectMapper(); - @Override + @Override @AfterEach public void tearDown() { try { Files.delete(test); - } catch (Exception e) { } + } catch (Exception ignored) { } } @Test - public void canSaveContentsIntoFile() { - File result = Unirest.get(MockServer.GET) + void canSaveContentsIntoFile() { + var result = Unirest.get(MockServer.GET) .queryString("talking","heads") .queryString("param3", "こんにちは") .asFile(test.toString()) @@ -67,8 +70,8 @@ public void canSaveContentsIntoFile() { } @Test - public void canSaveContentsIntoFileAsync() throws Exception { - File result = Unirest.get(MockServer.GET) + void canSaveContentsIntoFileAsync() throws Exception { + var result = Unirest.get(MockServer.GET) .queryString("talking","heads") .queryString("param3", "こんにちは") .asFileAsync(test.toString()) @@ -84,7 +87,7 @@ public void canSaveContentsIntoFileAsync() throws Exception { } @Test - public void canSaveContentsIntoFileAsyncWithCallback() throws Exception { + void canSaveContentsIntoFileAsyncWithCallback() throws Exception { Unirest.get(MockServer.GET) .queryString("talking","heads") .queryString("param3", "こんにちは") @@ -101,13 +104,65 @@ public void canSaveContentsIntoFileAsyncWithCallback() throws Exception { } @Test - public void canDownloadABinaryFile() throws Exception { - File f1 = TestUtil.rezFile("/image.jpg"); + void canDownloadABinaryFile() throws Exception { + var f1 = TestUtil.rezFile("/spidey.jpg"); - File f2 = Unirest.get(MockServer.BINARYFILE) + 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/src/test/java/BehaviorTests/AsGenericTypeTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/AsGenericTypeTest.java similarity index 80% rename from unirest/src/test/java/BehaviorTests/AsGenericTypeTest.java rename to unirest-bdd-tests/src/test/java/BehaviorTests/AsGenericTypeTest.java index cadef092f..86528685c 100644 --- a/unirest/src/test/java/BehaviorTests/AsGenericTypeTest.java +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/AsGenericTypeTest.java @@ -25,18 +25,18 @@ package BehaviorTests; -import kong.unirest.GsonObjectMapper; -import kong.unirest.Unirest; -import kong.unirest.GenericType; -import org.junit.Test; +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.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; -public class AsGenericTypeTest extends BddTest { +class AsGenericTypeTest extends BddTest { private final List foos = Arrays.asList( new Foo("foo"), @@ -45,10 +45,10 @@ public class AsGenericTypeTest extends BddTest { ); @Test - public void canGetAListOfObjects() { + void canGetAListOfObjects() { MockServer.setJsonAsResponse(foos); - List foos = Unirest.get(MockServer.GET) + var foos = Unirest.get(MockServer.GET) .asObject(new GenericType>(){}) .getBody(); @@ -56,10 +56,10 @@ public void canGetAListOfObjects() { } @Test - public void canGetAListOfObjectsAsync() throws ExecutionException, InterruptedException { + void canGetAListOfObjectsAsync() throws ExecutionException, InterruptedException { MockServer.setJsonAsResponse(foos); - List foos = Unirest.get(MockServer.GET) + var foos = Unirest.get(MockServer.GET) .asObjectAsync(new GenericType>(){}) .get() .getBody(); @@ -68,7 +68,7 @@ public void canGetAListOfObjectsAsync() throws ExecutionException, InterruptedEx } @Test - public void canGetAListOfObjectsAsyncWithCallback() { + void canGetAListOfObjectsAsyncWithCallback() { MockServer.setJsonAsResponse(foos); Unirest.get(MockServer.GET) @@ -83,28 +83,28 @@ public void canGetAListOfObjectsAsyncWithCallback() { } @Test - public void soManyLayersOfGenerics() { + void soManyLayersOfGenerics() { MockServer.setJsonAsResponse(new WeirdType<>(foos, "hey")); - WeirdType> foos = Unirest.get(MockServer.GET) + var foos = Unirest.get(MockServer.GET) .asObject(new GenericType>>(){}) .getBody(); - List someTees = foos.getSomeTees(); + var someTees = foos.getSomeTees(); assertTheFoos(someTees); } @Test - public void itAlsoWorksWithGson() { + void itAlsoWorksWithGson() { Unirest.config().setObjectMapper(new GsonObjectMapper()); MockServer.setJsonAsResponse(new WeirdType<>(foos, "hey")); - WeirdType> foos = Unirest.get(MockServer.GET) + var foos = Unirest.get(MockServer.GET) .asObject(new GenericType>>(){}) .getBody(); - List someTees = foos.getSomeTees(); + var someTees = foos.getSomeTees(); assertTheFoos(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/src/test/java/BehaviorTests/AsStringTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/AsStringTest.java similarity index 61% rename from unirest/src/test/java/BehaviorTests/AsStringTest.java rename to unirest-bdd-tests/src/test/java/BehaviorTests/AsStringTest.java index 54d731736..51ca098ae 100644 --- a/unirest/src/test/java/BehaviorTests/AsStringTest.java +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/AsStringTest.java @@ -25,69 +25,81 @@ package BehaviorTests; -import org.junit.Test; -import kong.unirest.HttpResponse; -import kong.unirest.TestUtil; -import kong.unirest.Unirest; + +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.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class AsStringTest extends BddTest { -public class AsStringTest extends BddTest { + @Test + void simpleExample() { + MockServer.setStringResponse("Hi Mom"); + var body = Unirest.get(MockServer.GET).asString().getBody(); + assertEquals("Hi Mom", body); + } @Test - public void whenNoBodyIsReturned() { - HttpResponse i = Unirest.get(MockServer.NOBODY).asString(); + void whenNoBodyIsReturned() { + var i = Unirest.get(MockServer.NOBODY).asString(); assertEquals(200, i.getStatus()); assertEquals("", i.getBody()); } @Test - public void canParseGzippedStringResponse() { - HttpResponse i = Unirest.get(MockServer.GZIP) + void canParseGzippedStringResponse() { + var i = Unirest.get(MockServer.GZIP) .queryString("foo", "bar") + .header(Header.ACCEPT_ENCODING, "gzip") .asString(); - RequestCapture cap = TestUtil.readValue(i.getBody(), RequestCapture.class); assertEquals(200, i.getStatus()); + + var cap = TestUtil.readValue(i.getBody(), RequestCapture.class); cap.assertParam("foo", "bar"); } @Test - public void canParseGzippedResponseAsync() throws Exception { - HttpResponse i = Unirest.get(MockServer.GZIP) + void canParseGzippedResponseAsync() throws Exception { + var i = Unirest.get(MockServer.GZIP) .queryString("foo", "bar") .asStringAsync().get(); - RequestCapture cap = TestUtil.readValue(i.getBody(), RequestCapture.class); assertEquals(200, i.getStatus()); + + var cap = TestUtil.readValue(i.getBody(), RequestCapture.class); cap.assertParam("foo", "bar"); } @Test - public void canGetBinaryResponse() { + void canGetBinaryResponse() { HttpResponse i = Unirest.get(MockServer.GET) .queryString("foo", "bar") .asString(); - RequestCapture cap = TestUtil.readValue(i.getBody(), RequestCapture.class); + var cap = TestUtil.readValue(i.getBody(), RequestCapture.class); cap.assertParam("foo", "bar"); } @Test - public void canGetBinaryResponseAsync() throws Exception { - CompletableFuture> r = Unirest.get(MockServer.GET) + void canGetBinaryResponseAsync() throws Exception { + var r = Unirest.get(MockServer.GET) .queryString("foo", "bar") .asStringAsync(); - RequestCapture cap = TestUtil.readValue(r.get().getBody(), RequestCapture.class); + var cap = TestUtil.readValue(r.get().getBody(), RequestCapture.class); cap.assertParam("foo", "bar"); } @Test - public void canGetBinaryResponseAsyncWithCallback() { + void canGetBinaryResponseAsyncWithCallback() { Unirest.get(MockServer.GET) .queryString("foo", "bar") .asStringAsync(r -> { @@ -100,14 +112,14 @@ public void canGetBinaryResponseAsyncWithCallback() { } @Test - public void unicodeResponse() { + void unicodeResponse() { MockServer.setStringResponse("ěščřžýáíé"); assertEquals("ěščřžýáíé", Unirest.get(MockServer.GET).asString().getBody()); } @Test - public void unicodeResponseAsync() throws Exception { + void unicodeResponseAsync() throws Exception { MockServer.setStringResponse("ěščřžýáíé"); Unirest.get(MockServer.GET) @@ -119,18 +131,24 @@ public void unicodeResponseAsync() throws Exception { assertAsync(); } - @Test - public void canSetExpectedCharsetOfResponse() { - assertEquals("šžýáíé", Unirest.get(MockServer.WINDOWS_LATIN_1_FILE) + @Test @Disabled + void canSetExpectedCharsetOfResponse() { + var response = Unirest.get(MockServer.WINDOWS_LATIN_1_FILE) .responseEncoding("windows-1250") - .asString().getBody()); + .asString(); + + assertEquals(200, response.getStatus()); + assertEquals("šžýáíé", response.getBody()); } - @Test - public void canSetDefaultCharsetOfResponse() { + @Test @Disabled + void canSetDefaultCharsetOfResponse() { Unirest.config().setDefaultResponseEncoding("windows-1250"); - assertEquals("šžýáíé", Unirest.get(MockServer.WINDOWS_LATIN_1_FILE) - .asString().getBody()); + 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/src/test/java/BehaviorTests/GZipTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/BaseUrlTest.java similarity index 62% rename from unirest/src/test/java/BehaviorTests/GZipTest.java rename to unirest-bdd-tests/src/test/java/BehaviorTests/BaseUrlTest.java index 34083ef28..443030c11 100644 --- a/unirest/src/test/java/BehaviorTests/GZipTest.java +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/BaseUrlTest.java @@ -25,39 +25,40 @@ package BehaviorTests; -import kong.unirest.Unirest; -import org.junit.Test; +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; + +class BaseUrlTest extends BddTest { -public class GZipTest extends BddTest { @Test - public void testGzip() { - Unirest.get(MockServer.GZIP) - .queryString("zipme", "up") + void canConfigureADefaultBaseUrl() { + Unirest.config().defaultBaseUrl(MockServer.HOST); + + Unirest.get("/get") .asObject(RequestCapture.class) .getBody() - .assertParam("zipme", "up") - .assertHeader("Accept-Encoding","gzip"); + .assertUrl(MockServer.GET); } @Test - public void testGzipAsync() throws Exception { - Unirest.get(MockServer.GZIP) - .queryString("zipme", "up") - .asObjectAsync(RequestCapture.class) - .get() + void willNotUseTheDefaultIfOverridden() { + Unirest.config().defaultBaseUrl("http://somewhere"); + + Unirest.get(MockServer.GET) + .asObject(RequestCapture.class) .getBody() - .assertParam("zipme", "up") - .assertHeader("Accept-Encoding","gzip"); + .assertUrl(MockServer.GET); } @Test - public void canDisableGZip() throws Exception { - Unirest.config().requestCompression(false); + void defaultUrlsCanHavePlaceholders() { + Unirest.config().defaultBaseUrl(MockServer.PASSED_PATH_PARAM); - Unirest.get(MockServer.GET) - .asObjectAsync(RequestCapture.class) - .get() + Unirest.get("/{another}") + .routeParam("params", "George") + .routeParam("another", "Ringo") + .asObject(RequestCapture.class) .getBody() - .assertNoHeader("Accept-Encoding"); + .assertUrl("http://localhost:4567/get/George/passed/Ringo"); } } diff --git a/unirest/src/test/java/BehaviorTests/BddTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/BddTest.java similarity index 81% rename from unirest/src/test/java/BehaviorTests/BddTest.java rename to unirest-bdd-tests/src/test/java/BehaviorTests/BddTest.java index 6bfa81f9e..512cebb93 100644 --- a/unirest/src/test/java/BehaviorTests/BddTest.java +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/BddTest.java @@ -25,40 +25,44 @@ package BehaviorTests; -import kong.unirest.*; -import org.junit.After; -import org.junit.Before; +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.io.IOException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; public class BddTest { - private static JacksonObjectMapper objectMapper = new JacksonObjectMapper(); + private static final JacksonObjectMapper objectMapper = new JacksonObjectMapper(); private CountDownLatch lock; private boolean status; private String fail; - @Before + @BeforeEach public void setUp() { + Unirest.shutDown(true); //TestUtil.debugApache(); MockServer.reset(); Unirest.config().setObjectMapper(objectMapper); lock = new CountDownLatch(1); status = false; + TestSSEConsumer.reset(); } - @After + @AfterEach public void tearDown() { Unirest.shutDown(true); + TestUtil.reset(); } public void assertAsync() { try { lock.await(5, TimeUnit.SECONDS); - assertTrue("Expected a async call but it never responded", status); + assertTrue(status, "Expected a async call but it never responded"); } catch (InterruptedException e) { throw new RuntimeException(e); } @@ -66,7 +70,7 @@ public void assertAsync() { public void assertFailed(String message) throws InterruptedException { lock.await(5, TimeUnit.SECONDS); - assertFalse("Should have failed", status); + assertFalse(status, "Should have failed"); assertEquals(message, fail); } 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..7125175cf --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/BodyLogSummaryTest.java @@ -0,0 +1,207 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF 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 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 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..7d60bbbe6 --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/CachingTest.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.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 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/src/test/java/BehaviorTests/DefectTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/DefectTest.java similarity index 60% rename from unirest/src/test/java/BehaviorTests/DefectTest.java rename to unirest-bdd-tests/src/test/java/BehaviorTests/DefectTest.java index ad2d80c6d..d9307d60a 100644 --- a/unirest/src/test/java/BehaviorTests/DefectTest.java +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/DefectTest.java @@ -25,26 +25,20 @@ package BehaviorTests; -import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; -import org.apache.http.nio.client.HttpAsyncClient; -import org.json.JSONObject; -import org.junit.Ignore; -import kong.unirest.Unirest; -import org.junit.Test; +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.io.IOException; import java.util.HashMap; import java.util.Map; -import java.util.stream.IntStream; -import static junit.framework.TestCase.assertNotSame; -import static junit.framework.TestCase.assertSame; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; -public class DefectTest extends BddTest { +class DefectTest extends BddTest { @Test - public void hashOnLinksDoNotMessUpUri() { + void hashOnLinksDoNotMessUpUri() { Unirest.get(MockServer.GET + "?a=1&b=2#some_location") .asObject(RequestCapture.class) .getBody() @@ -53,7 +47,7 @@ public void hashOnLinksDoNotMessUpUri() { } @Test - public void nullAndObjectValuesInMap() { + void nullAndObjectValuesInMap() { Map queryParams = new HashMap<>(); queryParams.put("foo", null); queryParams.put("baz", "qux"); @@ -67,29 +61,8 @@ public void nullAndObjectValuesInMap() { .assertQueryString("foo&baz=qux"); } - - @Test - public void issue_41_IllegalThreadStateExceptionUnderHighLoad() throws IOException { - Unirest.get(MockServer.GET).asStringAsync(); - - HttpAsyncClient first = Unirest.config().getAsyncClient().getClient(); - IntStream.range(1, 50).forEach(i ->{ - assertSame(first, Unirest.config().getAsyncClient().getClient()); - }); - - ((CloseableHttpAsyncClient)Unirest.config().getAsyncClient().getClient()).close(); - Unirest.get(MockServer.GET).asStringAsync(); - - HttpAsyncClient second = Unirest.config().getAsyncClient().getClient(); - assertNotSame(first, second); - - IntStream.range(1, 50).forEach(i ->{ - assertSame(second, Unirest.config().getAsyncClient().getClient()); - }); - } - - @Test @Ignore - public void trySomeoneElsesGZip() throws Exception { + @Test @Disabled + void trySomeoneElsesGZip() throws Exception { JSONObject body = Unirest.get("http://httpbin.org/gzip") .asJsonAsync().get().getBody().getObject(); 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/src/test/java/BehaviorTests/ErrorThing.java b/unirest-bdd-tests/src/test/java/BehaviorTests/ErrorThing.java similarity index 91% rename from unirest/src/test/java/BehaviorTests/ErrorThing.java rename to unirest-bdd-tests/src/test/java/BehaviorTests/ErrorThing.java index f18d9899d..b71f3aa43 100644 --- a/unirest/src/test/java/BehaviorTests/ErrorThing.java +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/ErrorThing.java @@ -28,6 +28,8 @@ 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; @@ -40,4 +42,8 @@ public ErrorThing(@JsonProperty("message") String 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/src/test/java/BehaviorTests/FormPostingTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/FormPostingTest.java similarity index 85% rename from unirest/src/test/java/BehaviorTests/FormPostingTest.java rename to unirest-bdd-tests/src/test/java/BehaviorTests/FormPostingTest.java index 7f854a1bf..efd5f9da3 100644 --- a/unirest/src/test/java/BehaviorTests/FormPostingTest.java +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/FormPostingTest.java @@ -25,21 +25,18 @@ package BehaviorTests; -import kong.unirest.*; -import org.junit.Test; +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; -import java.io.*; -import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import static java.util.Arrays.asList; -import static kong.unirest.TestUtil.rezFile; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; -public class FormPostingTest extends BddTest { +class FormPostingTest extends BddTest { @Test - public void testFormFields() { + void testFormFields() { Unirest.post(MockServer.POST) .header("accept", "application/json") .field("param1", "value1") @@ -52,7 +49,7 @@ public void testFormFields() { } @Test - public void formPostAsync() { + void formPostAsync() { Unirest.post(MockServer.POST) .header("accept", "application/json") .field("param1", "value1") @@ -68,7 +65,7 @@ public void formPostAsync() { } @Test - public void testAsyncCustomContentTypeAndFormParams() { + void testAsyncCustomContentTypeAndFormParams() { Unirest.post(MockServer.POST) .header("accept", "application/json") .header("Content-Type", "application/x-www-form-urlencoded") @@ -84,7 +81,7 @@ public void testAsyncCustomContentTypeAndFormParams() { } @Test - public void testPostArray() { + void testPostArray() { Unirest.post(MockServer.POST) .field("name", "Mark") .field("name", "Tom") @@ -96,7 +93,7 @@ public void testPostArray() { } @Test - public void testPostUTF8() { + void testPostUTF8() { Unirest.post(MockServer.POST) .header("accept", "application/json") .field("param3", "こんにちは") @@ -107,7 +104,7 @@ public void testPostUTF8() { } @Test - public void testPostCollection() { + void testPostCollection() { Unirest.post(MockServer.POST) .field("name", asList("Mark", "Tom")) .asObject(RequestCapture.class) @@ -118,7 +115,7 @@ public void testPostCollection() { } @Test - public void nullMapDoesntBomb() { + void nullMapDoesntBomb() { Unirest.post(MockServer.POST) .queryString("foo","bar") .fields(null) @@ -129,7 +126,7 @@ public void nullMapDoesntBomb() { } @Test - public void testDelete() { + void testDelete() { Unirest.delete(MockServer.DELETE) .field("name", "mark") .field("foo", "bar") @@ -141,25 +138,35 @@ public void testDelete() { } @Test - public void testChangingEncodingToForms(){ + void testChangingEncodingToForms(){ Unirest.post(MockServer.POST) .charset(StandardCharsets.US_ASCII) .field("foo", "bar") .asObject(RequestCapture.class) .getBody() - .assertContentType("application/x-www-form-urlencoded; charset=US-ASCII") + .assertRawContentType("application/x-www-form-urlencoded; charset=US-ASCII") .assertParam("foo", "bar") .assertCharset(StandardCharsets.US_ASCII); } @Test - public void testChangingEncodingAfterMovingToForm(){ + 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") + .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/src/test/java/BehaviorTests/GenericMappingTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/GenericMappingTest.java similarity index 83% rename from unirest/src/test/java/BehaviorTests/GenericMappingTest.java rename to unirest-bdd-tests/src/test/java/BehaviorTests/GenericMappingTest.java index eeedff7b0..d1fca3828 100644 --- a/unirest/src/test/java/BehaviorTests/GenericMappingTest.java +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/GenericMappingTest.java @@ -25,15 +25,16 @@ package BehaviorTests; -import kong.unirest.HttpResponse; -import kong.unirest.Unirest; -import org.junit.Test; +import kong.unirest.core.HttpResponse; +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class GenericMappingTest extends BddTest { -public class GenericMappingTest extends BddTest { @Test - public void canMapBody() { + void canMapBody() { MockServer.setStringResponse("123456"); int body = Unirest.get(MockServer.GET).asString().mapBody(Integer::valueOf); @@ -42,11 +43,11 @@ public void canMapBody() { } @Test - public void canMapTheEntireResponseIntoAnotherResponse() { + void canMapTheEntireResponseIntoAnotherResponse() { MockServer.setStringResponse("123456"); MockServer.addResponseHeader("cheese","cheddar"); - HttpResponse response = Unirest.get(MockServer.GET) + var response = Unirest.get(MockServer.GET) .asString() .map(Integer::valueOf); diff --git a/unirest/src/test/java/BehaviorTests/GetResponse.java b/unirest-bdd-tests/src/test/java/BehaviorTests/GetResponse.java similarity index 100% rename from unirest/src/test/java/BehaviorTests/GetResponse.java rename to unirest-bdd-tests/src/test/java/BehaviorTests/GetResponse.java 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/src/test/java/BehaviorTests/HeaderTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/HeaderTest.java similarity index 67% rename from unirest/src/test/java/BehaviorTests/HeaderTest.java rename to unirest-bdd-tests/src/test/java/BehaviorTests/HeaderTest.java index 062fd6f61..c844fcafd 100644 --- a/unirest/src/test/java/BehaviorTests/HeaderTest.java +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/HeaderTest.java @@ -25,22 +25,22 @@ package BehaviorTests; -import kong.unirest.GetRequest; -import kong.unirest.HttpResponse; -import kong.unirest.JsonNode; -import kong.unirest.Unirest; -import org.junit.Ignore; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; -import static kong.unirest.TestUtil.assertBasicAuth; -import static kong.unirest.TestUtil.mapOf; - -public class HeaderTest extends BddTest { +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 - public void contentLengthIsSetWithBodies() { + void contentLengthIsSetWithBodies() { Unirest.post(MockServer.POST) .body("do do do do") .asObject(RequestCapture.class) @@ -49,7 +49,7 @@ public void contentLengthIsSetWithBodies() { } @Test - public void testHeadersOnGetRequests() { + void testHeadersOnGetRequests() { Unirest.get(MockServer.GET) .header("user-agent", "hello-world") .accept("application/cheese-wiz") @@ -60,7 +60,7 @@ public void testHeadersOnGetRequests() { } @Test - public void testBasicAuth() { + void testBasicAuth() { Unirest.get(MockServer.GET) .basicAuth("user", "password1!") .asObject(RequestCapture.class) @@ -70,7 +70,7 @@ public void testBasicAuth() { } @Test - public void unicodeBasicAuth() { + void unicodeBasicAuth() { Unirest.get(MockServer.GET) .basicAuth("こんにちは", "こんにちは") .asObject(RequestCapture.class) @@ -80,11 +80,11 @@ public void unicodeBasicAuth() { } @Test - public void testDefaultHeaders() { + void testDefaultHeaders() { Unirest.config().setDefaultHeader("X-Custom-Header", "hello"); Unirest.config().setDefaultHeader("user-agent", "foobar"); - HttpResponse jsonResponse = Unirest.get(MockServer.GET).asJson(); + var jsonResponse = Unirest.get(MockServer.GET).asJson(); parse(jsonResponse) .assertHeader("X-Custom-Header", "hello") @@ -103,8 +103,8 @@ public void testDefaultHeaders() { } @Test - public void testCaseInsensitiveHeaders() { - GetRequest request = Unirest.get(MockServer.GET) + void testCaseInsensitiveHeaders() { + var request = Unirest.get(MockServer.GET) .header("Name", "Marco"); assertEquals(1, request.getHeaders().size()); @@ -126,7 +126,7 @@ public void testCaseInsensitiveHeaders() { } @Test - public void headersOnMultipart() { + void headersOnMultipart() { Unirest.post(MockServer.POST) .field("one","a") .accept("application/json") @@ -143,7 +143,7 @@ public void headersOnMultipart() { } @Test - public void canPassHeadersAsMap() { + void canPassHeadersAsMap() { Unirest.post(MockServer.POST) .headers(mapOf("one", "foo", "two", "bar", "three", null)) .asObject(RequestCapture.class) @@ -154,7 +154,7 @@ public void canPassHeadersAsMap() { } @Test - public void basicAuthOnPosts() { + void basicAuthOnPosts() { Unirest.post(MockServer.POST) .basicAuth("user", "test") .asObject(RequestCapture.class) @@ -164,7 +164,7 @@ public void basicAuthOnPosts() { } @Test - public void canSetDefaultBasicAuth() { + void canSetDefaultBasicAuth() { Unirest.config().setDefaultBasicAuth("bob", "pass"); Unirest.config().setDefaultBasicAuth("user", "test"); @@ -176,7 +176,7 @@ public void canSetDefaultBasicAuth() { } @Test - public void canOverrideDefaultBasicAuth() { + void canOverrideDefaultBasicAuth() { Unirest.config().setDefaultBasicAuth("bob", "pass"); Unirest.post(MockServer.POST) @@ -194,7 +194,7 @@ public void canOverrideDefaultBasicAuth() { } @Test - public void willNotCacheBasicAuth() { + void willNotCacheBasicAuth() { Unirest.get(MockServer.GET) .basicAuth("george","guitar") .asObject(RequestCapture.class) @@ -214,7 +214,7 @@ public void willNotCacheBasicAuth() { } @Test - public void willNotCacheHeadersAccrossRequests() { + void willNotCacheHeadersAccrossRequests() { Unirest.get(MockServer.GET) .header("foo", "bar") .asObject(RequestCapture.class) @@ -234,7 +234,7 @@ public void willNotCacheHeadersAccrossRequests() { } @Test - public void doesNotCacheAcrossTypes(){ + void doesNotCacheAcrossTypes(){ Unirest.get(MockServer.GET) .basicAuth("user1","pass1") .asObject(RequestCapture.class) @@ -248,38 +248,40 @@ public void doesNotCacheAcrossTypes(){ .assertBasicAuth("user2", "pass2"); } - @Test @Ignore - public void doesNotCacheAuthAcrossDomains(){ + @Test @Disabled + void doesNotCacheAuthAcrossDomains(){ Unirest.get(MockServer.GET) .basicAuth("user1","pass1") .asObject(RequestCapture.class) .getBody() .assertBasicAuth("user1","pass1"); - JsonNode bin = Unirest.post("http://httpbin.org/post") + var bin = Unirest.post("http://httpbin.org/post") .basicAuth("user2", "pass2") .asJson() .getBody(); String header = bin.getObject().getJSONObject("headers").getString("Authorization"); - assertBasicAuth(header, "user2", "pass2"); + 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 - public void canHaveTheSameHeaderAddedTwice() { - Unirest.config().setDefaultHeader("foo", "bar"); + void canHaveTheSameHeaderAddedTwice() { + Unirest.config().setDefaultHeader("x-fruit", "orange"); Unirest.get(MockServer.GET) - .header("foo", "qux") + .header("x-fruit", "apple") .asObject(RequestCapture.class) .getBody() - .assertHeaderSize("foo", 2) - .assertHeader("foo", "bar") - .assertHeader("foo", "qux"); + .assertHeaderSize("x-fruit", 2) + .assertHeader("x-fruit", "orange") + .assertHeader("x-fruit", "apple"); } @Test - public void canReplaceAHeader() { + void canReplaceAHeader() { Unirest.config().setDefaultHeader("foo", "bar"); Unirest.get(MockServer.GET) .headerReplace("foo", "qux") @@ -293,7 +295,7 @@ public void canReplaceAHeader() { } @Test - public void setVsAddDefaultHeaders() { + void setVsAddDefaultHeaders() { Unirest.config().setDefaultHeader("foo", "bar") .setDefaultHeader("foo", "qux") .addDefaultHeader("fruit", "mango") @@ -316,7 +318,7 @@ public void setVsAddDefaultHeaders() { } @Test - public void canSetAHeaderAsASupplier() { + void canSetAHeaderAsASupplier() { Unirest.config().setDefaultHeader("trace", () -> value); Unirest.get(MockServer.GET) @@ -333,7 +335,16 @@ public void canSetAHeaderAsASupplier() { } @Test - public void nullTests() { + 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) @@ -341,4 +352,97 @@ public void nullTests() { .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/src/test/java/BehaviorTests/InterceptorTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/InputStreamTest.java similarity index 57% rename from unirest/src/test/java/BehaviorTests/InterceptorTest.java rename to unirest-bdd-tests/src/test/java/BehaviorTests/InputStreamTest.java index dabdfb315..974d4815f 100644 --- a/unirest/src/test/java/BehaviorTests/InterceptorTest.java +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/InputStreamTest.java @@ -25,42 +25,40 @@ package BehaviorTests; -import kong.unirest.Unirest; -import org.apache.http.HttpException; -import org.apache.http.HttpRequest; -import org.apache.http.HttpRequestInterceptor; -import org.apache.http.protocol.HttpContext; -import org.junit.Test; +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; -import java.io.IOException; -import java.util.concurrent.ExecutionException; +import java.io.ByteArrayInputStream; -public class InterceptorTest extends BddTest { +public class InputStreamTest extends BddTest { @Test - public void canAddInterceptor() { - Unirest.config().addInterceptor(new TestInterceptor()); - - Unirest.get(MockServer.GET) + void canSendInputStreamAsBody() { + Unirest.post(MockServer.POST) + .body(new ByteArrayInputStream("Hi Mom".getBytes())) .asObject(RequestCapture.class) .getBody() - .assertHeader("x-custom", "foo"); + .assertBody("Hi Mom"); } @Test - public void canAddInterceptorToAsync() throws ExecutionException, InterruptedException { - Unirest.config().addInterceptor(new TestInterceptor()); - - Unirest.get(MockServer.GET) + void canSendInputStreamAsBodyAsync() throws Exception { + Unirest.post(MockServer.POST) + .body(new ByteArrayInputStream("Hi Mom".getBytes())) .asObjectAsync(RequestCapture.class) .get() .getBody() - .assertHeader("x-custom", "foo"); + .assertBody("Hi Mom"); } - private class TestInterceptor implements HttpRequestInterceptor { - @Override - public void process(HttpRequest httpRequest, HttpContext httpContext) throws HttpException, IOException { - httpRequest.addHeader("x-custom", "foo"); - } + @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/src/test/java/kong/unirest/JacksonObjectMapper.java b/unirest-bdd-tests/src/test/java/BehaviorTests/JacksonObjectMapper.java similarity index 54% rename from unirest/src/test/java/kong/unirest/JacksonObjectMapper.java rename to unirest-bdd-tests/src/test/java/BehaviorTests/JacksonObjectMapper.java index fe6f5cc8a..839f37a1f 100644 --- a/unirest/src/test/java/kong/unirest/JacksonObjectMapper.java +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/JacksonObjectMapper.java @@ -23,29 +23,33 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.module.SimpleModule; -import com.fasterxml.jackson.datatype.guava.GuavaModule; -import org.json.JSONObject; +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.IOException; import java.io.InputStream; -public class JacksonObjectMapper implements ObjectMapper { +public class JacksonObjectMapper implements kong.unirest.core.ObjectMapper { - private com.fasterxml.jackson.databind.ObjectMapper om = new com.fasterxml.jackson.databind.ObjectMapper(); + public tools.jackson.databind.ObjectMapper om; + + public JacksonObjectMapper(tools.jackson.databind.ObjectMapper om){ + this.om = om; + } public JacksonObjectMapper(){ - om.registerModule(new GuavaModule()); + 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()); @@ -53,13 +57,16 @@ public JacksonObjectMapper(){ simpleModule.addDeserializer(JsonPatch.class, new JsonPatchDeSerializer()); simpleModule.addSerializer(HttpMethod.class, new HttpMethodSerializer()); simpleModule.addDeserializer(HttpMethod.class, new HttpMethodDeSerializer()); - om.registerModule(simpleModule); + 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 (IOException e) { + } catch (JacksonException e) { throw new RuntimeException(e); } } @@ -68,7 +75,7 @@ public T readValue(File f, Class valueType){ public T readValue(String value, Class valueType) { try { return om.readValue(value, valueType); - } catch (IOException e) { + } catch (JacksonException e) { throw new UnirestException(e); } } @@ -77,7 +84,7 @@ public T readValue(String value, Class valueType) { public T readValue(String value, GenericType genericType) { try { return om.readValue(value, om.constructType(genericType.getType())); - } catch (IOException e) { + } catch (JacksonException e) { throw new UnirestException(e); } } @@ -86,7 +93,7 @@ public T readValue(String value, GenericType genericType) { public String writeValue(Object value) { try { return om.writeValueAsString(value); - } catch (JsonProcessingException e) { + } catch (JacksonException e) { throw new UnirestException(e); } } @@ -94,51 +101,59 @@ public String writeValue(Object value) { public T readValue(InputStream rawBody, Class as) { try { return om.readValue(rawBody, as); - } catch (IOException e) { + } catch (JacksonException e) { throw new RuntimeException(e); } } - public static class JsonPatchSerializer extends JsonSerializer{ - @Override - public void serialize(JsonPatch patch, JsonGenerator jgen, SerializerProvider serializerProvider) throws IOException { - jgen.writeRawValue(patch.toString()); + public T readValue(byte[] content, Class clazz) { + try { + return om.readValue(content, clazz); + } catch (JacksonException e) { + throw new RuntimeException(e); } } - public static class JsonPatchDeSerializer extends JsonDeserializer{ + 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) throws IOException { + public JsonPatch deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) { String s = jsonParser.readValueAsTree().toString(); return new JsonPatch(s); } } - public static class PatchSerializer extends JsonSerializer { - @Override - public void serialize(JsonPatchItem jwk, JsonGenerator jgen, SerializerProvider serializerProvider) throws IOException { - jgen.writeRaw(jwk.toString()); - } - } + 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 JsonDeserializer { + public static class PatchDeserializer extends ValueDeserializer { @Override - public JsonPatchItem deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { + public JsonPatchItem deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) { String s = jsonParser.readValueAsTree().toString(); return new JsonPatchItem(new JSONObject(s)); } } - public static class HttpMethodSerializer extends JsonSerializer { - @Override - public void serialize(HttpMethod httpMethod, JsonGenerator jgen, SerializerProvider serializerProvider) throws IOException { - jgen.writeString(httpMethod.name()); - } - } + 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 JsonDeserializer { + public static class HttpMethodDeSerializer extends ValueDeserializer { @Override - public HttpMethod deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { + public HttpMethod deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) { String s = jsonParser.readValueAs(String.class); return HttpMethod.valueOf(s); } diff --git a/unirest/src/test/java/kong/unirest/JacksonObjectMapperTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/JacksonObjectMapperTest.java similarity index 80% rename from unirest/src/test/java/kong/unirest/JacksonObjectMapperTest.java rename to unirest-bdd-tests/src/test/java/BehaviorTests/JacksonObjectMapperTest.java index 0b8ef4401..f1bbc923c 100644 --- a/unirest/src/test/java/kong/unirest/JacksonObjectMapperTest.java +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/JacksonObjectMapperTest.java @@ -23,19 +23,21 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package BehaviorTests; -import BehaviorTests.RequestCapture; -import org.junit.Test; + +import kong.unirest.core.JsonPatch; +import org.json.JSONException; +import org.junit.jupiter.api.Test; import org.skyscreamer.jsonassert.JSONAssert; -public class JacksonObjectMapperTest { +class JacksonObjectMapperTest { - private JacksonObjectMapper om = new JacksonObjectMapper(); + private final JacksonObjectMapper om = new JacksonObjectMapper(); @Test - public void jsonPatch() { - JsonPatch patch = new JsonPatch(); + void jsonPatch() throws JSONException { + var patch = new JsonPatch(); patch.add("/foo", "bar"); patch.add("/baz", "qux"); @@ -48,12 +50,12 @@ public void jsonPatch() { } @Test - public void jsonPatchInRequestCapture() { - JsonPatch patch = new JsonPatch(); + void jsonPatchInRequestCapture() throws JSONException { + var patch = new JsonPatch(); patch.add("/foo", "bar"); patch.add("/baz", "qux"); - RequestCapture rc = new RequestCapture(); + var rc = new RequestCapture(); rc.setPatch(patch); String actualStr = om.writeValue(rc); diff --git a/unirest/src/test/java/BehaviorTests/JankyProxy.java b/unirest-bdd-tests/src/test/java/BehaviorTests/JankyProxy.java similarity index 99% rename from unirest/src/test/java/BehaviorTests/JankyProxy.java rename to unirest-bdd-tests/src/test/java/BehaviorTests/JankyProxy.java index 5fc7f3cde..1dccb4319 100644 --- a/unirest/src/test/java/BehaviorTests/JankyProxy.java +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/JankyProxy.java @@ -113,7 +113,7 @@ public void run() { outToServer.flush(); wasUsedForClientToServer = true; } - } catch (IOException e) { + } catch (IOException ignored) { } try { outToServer.close(); 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/src/test/java/BehaviorTests/JsonPatchTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/JsonPatchTest.java similarity index 87% rename from unirest/src/test/java/BehaviorTests/JsonPatchTest.java rename to unirest-bdd-tests/src/test/java/BehaviorTests/JsonPatchTest.java index eee4e32cb..9b4b2f1fb 100644 --- a/unirest/src/test/java/BehaviorTests/JsonPatchTest.java +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/JsonPatchTest.java @@ -26,21 +26,19 @@ package BehaviorTests; import com.google.common.collect.ImmutableMap; -import kong.unirest.Unirest; -import org.json.JSONArray; -import org.json.JSONObject; -import org.junit.Test; +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 kong.unirest.TestUtil; -import java.io.IOException; -import static kong.unirest.JsonPatchOperation.*; +import static kong.unirest.core.JsonPatchOperation.*; -public class JsonPatchTest extends BddTest { +class JsonPatchTest extends BddTest { @Test - public void canAddThings() { + void canAddThings() { Unirest.jsonPatch(MockServer.PATCH) .add("/some/path", "a value") .add("/another/path", 42) @@ -54,7 +52,7 @@ public void canAddThings() { } @Test - public void canRemoveThings() { + void canRemoveThings() { Unirest.jsonPatch(MockServer.PATCH) .remove("/some/path") .remove("/another/path") @@ -68,7 +66,7 @@ public void canRemoveThings() { } @Test - public void canReplaceThings() { + void canReplaceThings() { Unirest.jsonPatch(MockServer.PATCH) .replace("/some/path", "a value") .replace("/another/path", 42) @@ -82,7 +80,7 @@ public void canReplaceThings() { } @Test - public void canTestThings() { + void canTestThings() { Unirest.jsonPatch(MockServer.PATCH) .test("/some/path", "a value") .test("/another/path", 42) @@ -96,7 +94,7 @@ public void canTestThings() { } @Test - public void canUseJsonForValues() { + void canUseJsonForValues() { Unirest.jsonPatch(MockServer.PATCH) .add("/some/path", new JSONObject().put("id", "foo")) .asObject(RequestCapture.class) @@ -105,7 +103,7 @@ public void canUseJsonForValues() { } @Test - public void lotsOfDifferentWaysToMakeObjects() { + void lotsOfDifferentWaysToMakeObjects() { JSONObject basicJson = new JSONObject().put("foo", "bar"); Unirest.jsonPatch(MockServer.PATCH) @@ -115,7 +113,7 @@ public void lotsOfDifferentWaysToMakeObjects() { .add("/jsonArrays", new JSONArray().put(basicJson)) .asObject(RequestCapture.class) .getBody() - .assertJsonPatch(add, "/stringArrays", new String[]{"foo, bar"}) + //.assertJsonPatch(add, "/stringArrays", new String[]{"foo, bar"}) .assertJsonPatch(add, "/maps", basicJson) .assertJsonPatch(add, "/jsonObjects", basicJson) .assertJsonPatch(add, "/jsonArrays", new JSONArray().put(basicJson)) @@ -123,7 +121,7 @@ public void lotsOfDifferentWaysToMakeObjects() { } @Test - public void canMoveObjects() { + void canMoveObjects() { Unirest.jsonPatch(MockServer.PATCH) .move("/old/location", "/new/location") .asObject(RequestCapture.class) @@ -132,7 +130,7 @@ public void canMoveObjects() { } @Test - public void canCopyObjects() { + void canCopyObjects() { Unirest.jsonPatch(MockServer.PATCH) .copy("/old/location", "/new/location") .asObject(RequestCapture.class) @@ -141,7 +139,7 @@ public void canCopyObjects() { } @Test - public void thatsSomeValidJson() throws IOException { + void thatsSomeValidJson() throws Exception { String patch = Unirest.jsonPatch(MockServer.PATCH) .add("/fruits/-", "Apple") .remove("/bugs") @@ -153,7 +151,7 @@ public void thatsSomeValidJson() throws IOException { .getBody() .body; - String expected = TestUtil.getResource("test-json-patch.json"); + var expected = TestUtil.getResource("test-json-patch.json"); JSONAssert.assertEquals(expected, patch, true); } diff --git a/unirest/src/test/java/BehaviorTests/MetricsTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/MetricsTest.java similarity index 68% rename from unirest/src/test/java/BehaviorTests/MetricsTest.java rename to unirest-bdd-tests/src/test/java/BehaviorTests/MetricsTest.java index 17ec52969..ad1b15aad 100644 --- a/unirest/src/test/java/BehaviorTests/MetricsTest.java +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/MetricsTest.java @@ -25,28 +25,23 @@ package BehaviorTests; -import kong.unirest.HttpRequestSummary; -import kong.unirest.Unirest; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpRequestBase; -import org.junit.Ignore; -import org.junit.Test; +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.hamcrest.Matchers.greaterThan; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class MetricsTest extends BddTest { +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.*; + +class MetricsTest extends BddTest { @Test - public void canSetAMetricObjectToInstramentUnirest() { - MyMetric metric = configureMetric(); + void canSetAMetricObjectToInstramentUnirest() { + var metric = configureMetric(); Unirest.get(GET).asEmpty(); Unirest.get(GET).asEmpty(); @@ -60,8 +55,8 @@ public void canSetAMetricObjectToInstramentUnirest() { } @Test - public void canGetParamedUrlInSummary() { - MyMetric metric = configureMetric(HttpRequestSummary::getRawPath); + void canGetParamedUrlInSummary() { + var metric = configureMetric(HttpRequestSummary::getRawPath); Unirest.get(PASSED_PATH_PARAM) .routeParam("params", "foo") @@ -72,8 +67,8 @@ public void canGetParamedUrlInSummary() { } @Test - public void canGetToMethod() { - MyMetric metric = configureMetric(s -> s.getHttpMethod().name()); + void canGetToMethod() { + var metric = configureMetric(s -> s.getHttpMethod().name()); Unirest.get(GET).asEmpty(); Unirest.get(GET).asEmpty(); @@ -85,8 +80,8 @@ public void canGetToMethod() { } @Test - public void amountOfTimeBetweenRequestAndResponseDoesNotIncludeDeserialization() { - MyMetric metric = configureMetric(); + void amountOfTimeBetweenRequestAndResponseDoesNotIncludeDeserialization() { + var metric = configureMetric(); Unirest.get(GET).asObject(r -> { assertEquals(1, metric.routes.get(GET).size()); @@ -95,8 +90,8 @@ public void amountOfTimeBetweenRequestAndResponseDoesNotIncludeDeserialization() } @Test - public void canGetInformationOnStatus() { - MyMetric metric = configureMetric(HttpRequestSummary::getUrl); + void canGetInformationOnStatus() { + var metric = configureMetric(HttpRequestSummary::getUrl); Unirest.get(GET).asEmpty(); Unirest.get(REDIRECT).asEmpty(); @@ -109,8 +104,8 @@ public void canGetInformationOnStatus() { } @Test - public void metricsOnAsyncRequests() throws Exception { - MyMetric metric = configureMetric(); + void metricsOnAsyncRequests() throws Exception { + var metric = configureMetric(); Unirest.get(GET).asEmptyAsync().get(); @@ -118,8 +113,8 @@ public void metricsOnAsyncRequests() throws Exception { } @Test - public void metricsOnAsyncMethodsWithCallbacks() { - MyMetric metric = configureMetric(); + void metricsOnAsyncMethodsWithCallbacks() { + var metric = configureMetric(); Unirest.get(GET).asEmptyAsync(r -> asyncSuccess()); @@ -128,25 +123,20 @@ public void metricsOnAsyncMethodsWithCallbacks() { } @Test - public void errorHandling() throws Exception { - HttpClient mock = mock(HttpClient.class); - when(mock.execute(any(HttpRequestBase.class))).thenThrow(new RuntimeException("boo")); - MyMetric metric = new MyMetric(HttpRequestSummary::getUrl); - Unirest.config().reset().httpClient(mock).instrumentWith(metric); + void errorHandling() throws Exception { + var metric = new MyMetric(HttpRequestSummary::getUrl); + Unirest.config().reset().httpClient(TestUtil.getFailureClient()).instrumentWith(metric); - try { - Unirest.get(GET).asEmpty(); - }catch (Exception e){ + assertThatThrownBy(() -> Unirest.get(GET).asEmpty()); - } - - assertEquals("boo", metric.routes.get(GET).get(0).e.getMessage()); + assertEquals("Something horrible happened", metric.routes.get(GET).get(0).e.getMessage()); } - @Test @Ignore - public void errorHandling_async() throws Exception { + @Test @Disabled + void errorHandling_async() throws Exception { MyMetric metric = configureMetric(); + try { Unirest.get("http://localhost:0000").asEmptyAsync().get(); }catch (Exception e){ @@ -160,7 +150,7 @@ public void errorHandling_async() throws Exception { long exTime; @Test - public void metricAsALambda() { + void metricAsALambda() { exTime = 0; Unirest.config().instrumentWith((s) -> { long startNanos = System.nanoTime(); @@ -169,12 +159,12 @@ public void metricAsALambda() { Unirest.get(GET).asEmpty(); - assertThat(exTime, greaterThan(0L)); + assertTrue(exTime > 0L); } @Test - public void showWhatSparkDoes() { - HashMap map = Unirest.get(SPARKLE) + void showWhatJavalinDoes() { + var map = Unirest.get(JAVALIN) .routeParam("spark", "joy") .queryString("food", "hamberders") .queryString("colour", "red") @@ -184,7 +174,7 @@ public void showWhatSparkDoes() { 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(null, map.get("contextPath()")); + assertEquals("", map.get("contextPath()")); assertEquals("/sparkle/joy/yippy", map.get("pathInfo()")); assertEquals("food=hamberders&colour=red", map.get("queryString()")); } @@ -194,7 +184,7 @@ private MyMetric configureMetric() { } private MyMetric configureMetric(Function keyFunction) { - MyMetric metric = new MyMetric(keyFunction); + var metric = new MyMetric(keyFunction); Unirest.config().followRedirects(false).instrumentWith(metric); return metric; } diff --git a/unirest/src/test/java/kong/unirest/MockCallback.java b/unirest-bdd-tests/src/test/java/BehaviorTests/MockCallback.java similarity index 91% rename from unirest/src/test/java/kong/unirest/MockCallback.java rename to unirest-bdd-tests/src/test/java/BehaviorTests/MockCallback.java index ec9b6148f..531f61a25 100644 --- a/unirest/src/test/java/kong/unirest/MockCallback.java +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/MockCallback.java @@ -23,9 +23,12 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package BehaviorTests; -import BehaviorTests.BddTest; +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; @@ -35,7 +38,7 @@ public static MockCallback json(BddTest test){ return new MockCallback<>(test); } - private BddTest test; + private final BddTest test; private Consumer> onSuccess = r -> {}; private Consumer onFail = f -> {}; private Runnable onCancel = () -> {}; 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..d082788cc --- /dev/null +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/MockServer.java @@ -0,0 +1,449 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF 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 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/"); + + }).start(PORT); + app.error(404, MockServer::notFound); + app.before(c -> { + timesCalled++; + lastRequest = new RequestCapture(c); + }); + app.before("/basic", ctx -> { + if (ctx.basicAuthCredentials() == null) { + ctx.header("WWW-Authenticate", "Basic realm=\"User Visible Realm\", charset=\"UTF-8\""); + ctx.status(401); + } + }); + app.ws("/websocket", ws); + app.sse("/sse", new TestSSEConsumer()); + app.delete("/delete", MockServer::jsonResponse); + app.get("/sparkle/{spark}/yippy", MockServer::sparkle); + app.post("/post", MockServer::jsonResponse); + app.get("/get", MockServer::jsonResponse); + app.get("/gzip", MockServer::gzipResponse); + app.post("/empty-gzip", MockServer::emptyGzipResponse); + app.get("/redirect", MockServer::redirect); + app.post("/redirect", MockServer::redirect); + app.patch("/patch", MockServer::jsonResponse); + app.get("/invalid", MockServer::inValid); + app.options("/get", MockServer::jsonResponse); + app.get("/nobody", MockServer::nobody); + app.head("/get", MockServer::jsonResponse); + app.put("/post", MockServer::jsonResponse); + app.get("/get/{params}/passed", MockServer::jsonResponse); + app.get("/get/{params}/passed/{another}", MockServer::jsonResponse); + app.get("/proxy", MockServer::proxiedResponse); + app.get("/binary", MockServer::file); + app.get("/paged", MockServer::paged); + app.post("/paged", MockServer::paged); + app.post("/raw", MockServer::echo); + app.get("/error", MockServer::error); + app.get("/hello", MockServer::helloWOrld); + app.get("/timeout", MockServer::timeout); + app.get("/basic", MockServer::basic); + 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/src/test/java/BehaviorTests/MultiPartFormPostingTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/MultiPartFormPostingTest.java similarity index 56% rename from unirest/src/test/java/BehaviorTests/MultiPartFormPostingTest.java rename to unirest-bdd-tests/src/test/java/BehaviorTests/MultiPartFormPostingTest.java index d88c19f09..0878e5236 100644 --- a/unirest/src/test/java/BehaviorTests/MultiPartFormPostingTest.java +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/MultiPartFormPostingTest.java @@ -25,37 +25,41 @@ package BehaviorTests; -import kong.unirest.*; -import org.junit.Test; +import kong.unirest.core.ContentType; +import kong.unirest.core.MultipartMode; +import kong.unirest.core.Unirest; +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.TestUtil.getFileBytes; -import static kong.unirest.TestUtil.rezFile; -import static org.junit.Assert.assertEquals; +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 { -public class MultiPartFormPostingTest extends BddTest { @Test - public void testMultipart() throws Exception { + void testMultipart() { Unirest.post(MockServer.POST) .field("name", "Mark") - .field("funky","bunch") - .field("file", rezFile("/test")) + .field("funky", "bunch") + .field("file", rezFile("/test.txt")) .asObject(RequestCapture.class) .getBody() .assertParam("name", "Mark") .assertMultiPartContentType() - .getFile("test") + .getFile("test.txt") .assertBody("This is a test file") .assertFileType("application/octet-stream"); } @Test - public void fileSizeDoesntChange() throws Exception { - File file = rezFile("/image.jpg"); - long size = file.length(); + void fileSizeDoesntChange() throws Exception { + var file = rezFile("/image.jpg"); + var size = file.length(); RequestCapture capture = Unirest.post(MockServer.POST) .field("file", file) @@ -69,7 +73,7 @@ public void fileSizeDoesntChange() throws Exception { } @Test - public void testMultipartContentType() { + void testMultipartContentType() { Unirest.post(MockServer.POST) .field("name", "Mark") .field("file", rezFile("/image.jpg"), "image/jpeg") @@ -82,8 +86,8 @@ public void testMultipartContentType() { } @Test - public void canSendRawInputStreamsWithoutAFileName() throws Exception { - FileInputStream stream = new FileInputStream(rezFile("/test")); + void canSendRawInputStreamsWithoutAFileName() throws Exception { + var stream = new FileInputStream(rezFile("/test.txt")); Unirest.post(MockServer.POST) .field("file", stream) @@ -93,8 +97,8 @@ public void canSendRawInputStreamsWithoutAFileName() throws Exception { } @Test - public void multipleFiles_sameField() { - RequestCapture body = Unirest.post(MockServer.POST) + void multipleFiles_sameField() { + var body = Unirest.post(MockServer.POST) .field("file", rezFile("/image.jpg")) .field("file", rezFile("/spidey.jpg")) .asObject(RequestCapture.class) @@ -106,7 +110,7 @@ public void multipleFiles_sameField() { } @Test - public void testMultipartInputStreamContentType() throws Exception { + void testMultipartInputStreamContentType() throws Exception { FileInputStream stream = new FileInputStream(rezFile("/image.jpg")); Unirest.post(MockServer.POST) @@ -116,21 +120,21 @@ public void testMultipartInputStreamContentType() throws Exception { .asObject(RequestCapture.class) .getBody() .assertMultiPartContentType() - .assertHeader("Accept", ContentType.MULTIPART_FORM_DATA.toString()) + .assertHeader("Accept", "multipart/form-data") .assertParam("name", "Mark") .getFile("image.jpg") .assertFileType("application/octet-stream"); } @Test - public void testMultipartInputStreamContentTypeAsync() throws Exception { + void testMultipartInputStreamContentTypeAsync() throws Exception { Unirest.post(MockServer.POST) .field("name", "Mark") - .field("file", new FileInputStream(rezFile("/test")), ContentType.APPLICATION_OCTET_STREAM, "test") + .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") + .getFile("test.txt") .assertFileType("application/octet-stream")) ); @@ -138,11 +142,11 @@ public void testMultipartInputStreamContentTypeAsync() throws Exception { } @Test - public void testMultipartByteContentType() throws Exception { - final byte[] bytes = getFileBytes("/image.jpg"); + void testMultipartByteContentType() throws Exception { + var bytes = getFileBytes("/image.jpg"); Unirest.post(MockServer.POST) - .field("boot","boot") + .field("boot", "boot") .field("file", bytes, "image.jpg") .asObject(RequestCapture.class) .getBody() @@ -152,17 +156,17 @@ public void testMultipartByteContentType() throws Exception { } @Test - public void testMultipartByteContentTypeAsync() throws Exception { - final byte[] bytes = getFileBytes("/test"); + void testMultipartByteContentTypeAsync() throws Exception { + var bytes = getFileBytes("/test.txt"); Unirest.post(MockServer.POST) .field("name", "Mark") - .field("file", bytes, "test") + .field("file", bytes, "test.txt") .asJsonAsync(new MockCallback<>(this, r -> parse(r) .assertParam("name", "Mark") .assertMultiPartContentType() - .getFile("test") + .getFile("test.txt") .assertFileType("application/octet-stream")) ); @@ -170,17 +174,16 @@ public void testMultipartByteContentTypeAsync() throws Exception { } - @Test - public void testMultipartAsync() throws Exception { + void testMultipartAsync() { Unirest.post(MockServer.POST) .field("name", "Mark") - .field("file", rezFile("/test")) + .field("file", rezFile("/test.txt")) .asJsonAsync(new MockCallback<>(this, r -> parse(r) .assertParam("name", "Mark") .assertMultiPartContentType() - .getFile("test") + .getFile("test.txt") .assertFileType("application/octet-stream") .assertBody("This is a test file")) ); @@ -189,9 +192,9 @@ public void testMultipartAsync() throws Exception { } @Test - public void utf8FileNames() { - InputStream fileData = new ByteArrayInputStream(new byte[] {'t', 'e', 's', 't'}); - final String filename = "fileäöü.pöf"; + 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) @@ -202,12 +205,20 @@ public void utf8FileNames() { .assertFileName(filename); } - + @Test + void simpleMultiPart() { + Unirest.post(MockServer.POST) + .field("foo", "bar") + .field("fruit", "apple") + .asObject(RequestCapture.class) + .getBody() + .assertParam("foo", "bar"); + } @Test - public void canSetModeToStrictForLegacySupport() { - InputStream fileData = new ByteArrayInputStream(new byte[] {'t', 'e', 's', 't'}); - final String filename = "fileäöü.pöf"; + 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) @@ -220,8 +231,8 @@ public void canSetModeToStrictForLegacySupport() { } @Test - public void canPostInputStreamWithContentType() throws Exception { - File file = TestUtil.getImageFile(); + 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) @@ -233,8 +244,8 @@ public void canPostInputStreamWithContentType() throws Exception { } @Test - public void canPostInputStream() throws Exception { - File file = TestUtil.getImageFile(); + void canPostInputStream() throws Exception { + var file = TestUtil.getImageFile(); Unirest.post(MockServer.POST) .field("testfile", new FileInputStream(file), "image.jpg") .asObject(RequestCapture.class) @@ -246,24 +257,24 @@ public void canPostInputStream() throws Exception { } @Test - public void postFieldsAsMap() throws URISyntaxException { - File file = TestUtil.getImageFile(); + 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() - .assertMultiPartContentType() .assertParam("big", "bird") .assertParam("charlie", "42") .assertParam("gonzo", "") + .assertMultiPartContentType() .getFile("image.jpg") .assertFileType("application/octet-stream"); } @Test - public void postFileWithoutContentType() { - File file = TestUtil.getImageFile(); + void postFileWithoutContentType() { + var file = TestUtil.getImageFile(); Unirest.post(MockServer.POST) .field("testfile", file) .asObject(RequestCapture.class) @@ -274,8 +285,8 @@ public void postFileWithoutContentType() { } @Test - public void postFileWithContentType() { - File file = TestUtil.getImageFile(); + void postFileWithContentType() { + var file = TestUtil.getImageFile(); Unirest.post(MockServer.POST) .field("testfile", file, ContentType.IMAGE_JPEG.getMimeType()) .asObject(RequestCapture.class) @@ -286,7 +297,7 @@ public void postFileWithContentType() { } @Test - public void multiPartInputStreamAsFile() throws FileNotFoundException { + void multiPartInputStreamAsFile() throws FileNotFoundException { Unirest.post(MockServer.POST) .field("foo", "bar") .field("filecontents", new FileInputStream(rezFile("/image.jpg")), "image.jpg") @@ -300,23 +311,23 @@ public void multiPartInputStreamAsFile() throws FileNotFoundException { } @Test - public void testPostMulipleFIles() { - RequestCapture cap = Unirest.post(MockServer.POST) - .field("name", asList(rezFile("/test"), rezFile("/test2"))) + 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").assertBody("This is a test file"); - cap.getFile("test2").assertBody("this is another test"); + cap.getFile("test.txt").assertBody("This is a test file"); + cap.getFile("test2.txt").assertBody("this is another test"); } @Test - public void testPostMultipleFiles()throws Exception { + void testPostMultipleFiles() throws Exception { Unirest.post(MockServer.POST) .field("param3", "wot") - .field("file1", rezFile("/test")) - .field("file2", rezFile("/test")) + .field("file1", rezFile("/test.txt")) + .field("file2", rezFile("/test.txt")) .asObject(RequestCapture.class) .getBody() .assertMultiPartContentType() @@ -326,11 +337,11 @@ public void testPostMultipleFiles()throws Exception { } @Test - public void testPostBinaryUTF8() throws Exception { + void testPostBinaryUTF8() throws Exception { Unirest.post(MockServer.POST) .header("Accept", ContentType.MULTIPART_FORM_DATA.getMimeType()) .field("param3", "こんにちは") - .field("file", rezFile("/test")) + .field("file", rezFile("/test.txt")) .asObject(RequestCapture.class) .getBody() .assertMultiPartContentType() @@ -339,9 +350,9 @@ public void testPostBinaryUTF8() throws Exception { } @Test - public void testMultipeInputStreams() throws FileNotFoundException { + void testMultipeInputStreams() throws FileNotFoundException { Unirest.post(MockServer.POST) - .field("name", asList(new FileInputStream(rezFile("/test")), new FileInputStream(rezFile("/test2")))) + .field("name", asList(new FileInputStream(rezFile("/test.txt")), new FileInputStream(rezFile("/test2.txt")))) .asObject(RequestCapture.class) .getBody() .assertMultiPartContentType() @@ -350,10 +361,10 @@ public void testMultipeInputStreams() throws FileNotFoundException { } @Test - public void multiPartInputStream() throws FileNotFoundException { + void multiPartInputStream() throws FileNotFoundException { Unirest.post(MockServer.POST) .field("foo", "bar") - .field("filecontents", new FileInputStream(rezFile("/test")), ContentType.WILDCARD) + .field("filecontents", new FileInputStream(rezFile("/test.txt")), ContentType.WILDCARD) .asObject(RequestCapture.class) .getBody() .assertMultiPartContentType() @@ -362,7 +373,7 @@ public void multiPartInputStream() throws FileNotFoundException { } @Test - public void passFileAsByteArray() { + void passFileAsByteArray() { Unirest.post(MockServer.POST) .field("foo", "bar") .field("filecontents", TestUtil.getFileBytes("/image.jpg"), ContentType.IMAGE_JPEG, "image.jpg") @@ -376,17 +387,16 @@ public void passFileAsByteArray() { } @Test - public void nullFileResultsInEmptyPost() { + void nullFileResultsInEmptyPost() { Unirest.post(MockServer.POST) - .field("testfile", (Object)null, ContentType.IMAGE_JPEG.getMimeType()) + .field("testfile", (Object) null, ContentType.IMAGE_JPEG.getMimeType()) .asObject(RequestCapture.class) .getBody() - .assertContentType("application/x-www-form-urlencoded; charset=UTF-8") .assertParam("testfile", ""); } @Test - public void canForceIntoMultiPart() { + void canForceIntoMultiPart() { Unirest.post(MockServer.POST) .multiPartContent() .field("foo", "bar") @@ -397,20 +407,126 @@ public void canForceIntoMultiPart() { } @Test - public void rawInspection() { - String body = Unirest.post(MockServer.ECHO_RAW) - .field("marky","mark") - .field("funky","bunch") - .field("file", rezFile("/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(); - String expected = TestUtil.getResource("rawPost.txt").replaceAll("\r","").trim(); + var expected = TestUtil.getResource("rawPostSorted.txt").replaceAll("\r", "").trim(); - String id = body.substring(2, body.indexOf("\n") -1); - body = body.replaceAll(id, "IDENTIFIER").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"); + // Lets create a way to tell unirest what the boundary should be so we can test it easier. + //h.assertRawValue("multipart/form-data; boundary=4ebf68bc-70f8-462b-b3a5-48dadb236af3;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 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"); + // Lets create a way to tell unirest what the boundary should be so we can test it easier. + //h.assertRawValue("multipart/form-data; boundary=4ebf68bc-70f8-462b-b3a5-48dadb236af3;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/src/test/java/BehaviorTests/MyMetric.java b/unirest-bdd-tests/src/test/java/BehaviorTests/MyMetric.java similarity index 93% rename from unirest/src/test/java/BehaviorTests/MyMetric.java rename to unirest-bdd-tests/src/test/java/BehaviorTests/MyMetric.java index 6d1cb03f1..dcfba531a 100644 --- a/unirest/src/test/java/BehaviorTests/MyMetric.java +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/MyMetric.java @@ -26,10 +26,10 @@ package BehaviorTests; import com.google.common.collect.ArrayListMultimap; -import kong.unirest.HttpRequestSummary; -import kong.unirest.HttpResponseSummary; -import kong.unirest.MetricContext; -import kong.unirest.UniMetric; +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; diff --git a/unirest/src/test/java/kong/unirest/NoopCallback.java b/unirest-bdd-tests/src/test/java/BehaviorTests/NoopCallback.java similarity index 91% rename from unirest/src/test/java/kong/unirest/NoopCallback.java rename to unirest-bdd-tests/src/test/java/BehaviorTests/NoopCallback.java index 14d94dc49..32afe5843 100644 --- a/unirest/src/test/java/kong/unirest/NoopCallback.java +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/NoopCallback.java @@ -23,7 +23,11 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package BehaviorTests; + +import kong.unirest.core.Callback; +import kong.unirest.core.HttpResponse; +import kong.unirest.core.UnirestException; public class NoopCallback implements Callback { @Override diff --git a/unirest/src/test/java/BehaviorTests/ObjectFunctionalTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/ObjectFunctionalTest.java similarity index 74% rename from unirest/src/test/java/BehaviorTests/ObjectFunctionalTest.java rename to unirest-bdd-tests/src/test/java/BehaviorTests/ObjectFunctionalTest.java index 15c7c1b1b..7dd4ee141 100644 --- a/unirest/src/test/java/BehaviorTests/ObjectFunctionalTest.java +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/ObjectFunctionalTest.java @@ -26,24 +26,26 @@ package BehaviorTests; import com.google.gson.Gson; -import kong.unirest.HttpResponse; -import org.junit.Test; -import kong.unirest.Unirest; +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.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; -public class ObjectFunctionalTest extends BddTest { - private Gson gson = new Gson(); +class ObjectFunctionalTest extends BddTest { + + private final Gson gson = new Gson(); @Test - public void canUseAFunctionToTransform() { + void canUseAFunctionToTransform() { MockServer.setJsonAsResponse(of("foo", "bar")); - Map r = Unirest.get(MockServer.GET) + var r = Unirest.get(MockServer.GET) .asObject(i -> gson.fromJson(i.getContentReader(), HashMap.class)) .getBody(); @@ -51,10 +53,10 @@ public void canUseAFunctionToTransform() { } @Test - public void canUseAFunctionToTransformAsync() throws Exception { + void canUseAFunctionToTransformAsync() throws Exception { MockServer.setJsonAsResponse(of("foo", "bar")); - Map r = Unirest.get(MockServer.GET) + var r = Unirest.get(MockServer.GET) .asObjectAsync(i -> gson.fromJson(i.getContentReader(), HashMap.class)) .get() .getBody(); @@ -63,10 +65,10 @@ public void canUseAFunctionToTransformAsync() throws Exception { } @Test - public void willNotStopForParsingExceptions() { + void willNotStopForParsingExceptions() { MockServer.setStringResponse("call me ishmael"); - RuntimeException ohNoes = new RuntimeException("oh noes"); + var ohNoes = new RuntimeException("oh noes"); HttpResponse r = Unirest.get(MockServer.GET) .asObject(i -> { @@ -74,22 +76,24 @@ public void willNotStopForParsingExceptions() { }); assertEquals(200, r.getStatus()); + assertTrue(r.getParsingError().isPresent()); assertEquals(ohNoes, r.getParsingError().get().getCause()); assertEquals("call me ishmael", r.getParsingError().get().getOriginalBody()); } @Test - public void willNotStopForParsingExceptions_async() throws Exception { + void willNotStopForParsingExceptions_async() throws Exception { MockServer.setStringResponse("call me ishmael"); - RuntimeException ohNoes = new RuntimeException("oh noes"); + var ohNoes = new RuntimeException("oh noes"); - HttpResponse r = Unirest.get(MockServer.GET) + 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/src/test/java/BehaviorTests/Pair.java b/unirest-bdd-tests/src/test/java/BehaviorTests/Pair.java similarity index 100% rename from unirest/src/test/java/BehaviorTests/Pair.java rename to unirest-bdd-tests/src/test/java/BehaviorTests/Pair.java 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/src/test/java/BehaviorTests/PostRequestHandlersTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/PostRequestHandlersTest.java similarity index 81% rename from unirest/src/test/java/BehaviorTests/PostRequestHandlersTest.java rename to unirest-bdd-tests/src/test/java/BehaviorTests/PostRequestHandlersTest.java index a5ea44eda..aa4a3e1a3 100644 --- a/unirest/src/test/java/BehaviorTests/PostRequestHandlersTest.java +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/PostRequestHandlersTest.java @@ -25,26 +25,28 @@ package BehaviorTests; -import org.junit.After; -import org.junit.Test; -import kong.unirest.HttpResponse; -import kong.unirest.Unirest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import kong.unirest.core.HttpResponse; +import kong.unirest.core.Unirest; -import static org.junit.Assert.*; +import java.util.function.Consumer; -public class PostRequestHandlersTest extends BddTest { +import static org.junit.jupiter.api.Assertions.*; + +class PostRequestHandlersTest extends BddTest { private HttpResponse captured; @Override - @After + @AfterEach public void tearDown() { super.tearDown(); captured = null; } @Test - public void onSuccessDoSomething() { + void onSuccessDoSomething() { Unirest.get(MockServer.GET) .queryString("foo", "bar") .asObject(RequestCapture.class) @@ -56,7 +58,7 @@ public void onSuccessDoSomething() { } @Test - public void onFailDoSomething() { + void onFailDoSomething() { Unirest.get(MockServer.INVALID_REQUEST) .queryString("foo", "bar") .asObject(RequestCapture.class) @@ -68,7 +70,7 @@ public void onFailDoSomething() { } @Test - public void itsAFailIfTheMapperFails() { + void itsAFailIfTheMapperFails() { MockServer.setStringResponse("not what you expect"); Unirest.get(MockServer.GET) @@ -83,11 +85,9 @@ public void itsAFailIfTheMapperFails() { assertEquals("not what you expect", captured.getParsingError().get().getOriginalBody()); } - - @Test - public void onSuccessBeSuccessful() { - HttpResponse response = Unirest.get(MockServer.GET) + void onSuccessBeSuccessful() { + var response = Unirest.get(MockServer.GET) .queryString("foo", "bar") .asObject(RequestCapture.class); @@ -95,8 +95,8 @@ public void onSuccessBeSuccessful() { } @Test - public void onFailBeUnsuccessful() { - HttpResponse response = Unirest.get(MockServer.INVALID_REQUEST) + void onFailBeUnsuccessful() { + var response = Unirest.get(MockServer.INVALID_REQUEST) .queryString("foo", "bar") .asObject(RequestCapture.class); @@ -104,10 +104,10 @@ public void onFailBeUnsuccessful() { } @Test - public void beUnsuccessfulIfTheMapperFails() { + void beUnsuccessfulIfTheMapperFails() { MockServer.setStringResponse("not what you expect"); - HttpResponse response = Unirest.get(MockServer.GET) + var response = Unirest.get(MockServer.GET) .queryString("foo", "bar") .asObject(RequestCapture.class); 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/src/test/java/BehaviorTests/QueryStringTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/QueryStringTest.java similarity index 83% rename from unirest/src/test/java/BehaviorTests/QueryStringTest.java rename to unirest-bdd-tests/src/test/java/BehaviorTests/QueryStringTest.java index 0be9afc26..408a15979 100644 --- a/unirest/src/test/java/BehaviorTests/QueryStringTest.java +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/QueryStringTest.java @@ -25,16 +25,17 @@ package BehaviorTests; -import kong.unirest.HttpResponse; -import kong.unirest.JsonNode; -import kong.unirest.Unirest; -import org.junit.Test; +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; -public class QueryStringTest extends BddTest { +class QueryStringTest extends BddTest { + @Test - public void testGetQueryStrings() { + void testGetQueryStrings() { Unirest.get(MockServer.GET) .queryString("name", "mark") .queryString("nick", "thefosk") @@ -45,7 +46,7 @@ public void testGetQueryStrings() { } @Test - public void canPassQueryParamsDirectlyOnUriOrWithMethod() { + void canPassQueryParamsDirectlyOnUriOrWithMethod() { Unirest.get(MockServer.GET + "?name=mark") .asObject(RequestCapture.class) .getBody() @@ -59,7 +60,7 @@ public void canPassQueryParamsDirectlyOnUriOrWithMethod() { } @Test - public void canPassInACharSequence() { + void canPassInACharSequence() { Unirest.get(MockServer.GET) .queryString("foo", new StringBuilder("bar")) .asObject(RequestCapture.class) @@ -68,7 +69,7 @@ public void canPassInACharSequence() { } @Test - public void multipleParams() { + void multipleParams() { Unirest.get(MockServer.GET + "?name=ringo") .queryString("name", "paul") .queryString("name", "john") @@ -80,7 +81,7 @@ public void multipleParams() { } @Test - public void testGetUTF8() { + void testGetUTF8() { Unirest.get(MockServer.GET) .queryString("param3", "こんにちは") .asObject(RequestCapture.class) @@ -89,17 +90,17 @@ public void testGetUTF8() { } @Test - public void testGetMultiple() { + void testGetMultiple() { for (int i = 1; i <= 20; i++) { - HttpResponse response = Unirest.get(MockServer.GET + "?try=" + i).asJson(); + var response = Unirest.get(MockServer.GET + "?try=" + i).asJson(); parse(response).assertParam("try", String.valueOf(i)); } } @Test - public void testQueryStringEncoding() { - String testKey = "email2=someKey&email"; - String testValue = "hello@hello.com"; + void testQueryStringEncoding() { + var testKey = "email2=someKey&email"; + var testValue = "hello@hello.com"; Unirest.get(MockServer.GET) .queryString(testKey, testValue) @@ -109,7 +110,7 @@ public void testQueryStringEncoding() { } @Test - public void testGetQuerystringArray() { + void testGetQuerystringArray() { Unirest.get(MockServer.GET) .queryString("name", "Mark") .queryString("name", "Tom") @@ -120,7 +121,7 @@ public void testGetQuerystringArray() { } @Test - public void testGetArray() { + void testGetArray() { Unirest.get(MockServer.GET) .queryString("name", Arrays.asList("Mark", "Tom")) .asObject(RequestCapture.class) diff --git a/unirest/src/test/java/BehaviorTests/CookieTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/RawUrlTest.java similarity index 60% rename from unirest/src/test/java/BehaviorTests/CookieTest.java rename to unirest-bdd-tests/src/test/java/BehaviorTests/RawUrlTest.java index 375a31abf..6af7e51e3 100644 --- a/unirest/src/test/java/BehaviorTests/CookieTest.java +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/RawUrlTest.java @@ -25,41 +25,34 @@ package BehaviorTests; -import kong.unirest.Unirest; -import org.apache.http.client.config.CookieSpecs; -import org.junit.Test; +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; + +class RawUrlTest extends BddTest { -public class CookieTest extends BddTest { @Test - public void willManageCookiesByDefault() { - MockServer.expectCookie("JSESSIONID", "ABC123"); - Unirest.get(MockServer.GET).asEmpty(); - Unirest.get(MockServer.GET) + void canCallRawPathWithSpace() { + Unirest.get(MockServer.GET + "/foo/passed/Moody Blues") .asObject(RequestCapture.class) .getBody() - .assertCookie("JSESSIONID", "ABC123"); + .assertUrl("http://localhost:4567/get/foo/passed/Moody%20Blues"); } @Test - public void canTurnOffCookieManagement() { - Unirest.config().enableCookieManagement(false); - MockServer.expectCookie("JSESSIONID", "ABC123"); - Unirest.get(MockServer.GET).asEmpty(); - Unirest.get(MockServer.GET) + void canCallRawPathWithTab() { + Unirest.get(MockServer.GET + "/foo/passed/Moody\tBlues") .asObject(RequestCapture.class) .getBody() - .assertNoCookie("JSESSIONID"); + .assertUrl("http://localhost:4567/get/foo/passed/Moody%09Blues"); } @Test - public void canSetCookieSpec() { - Unirest.config().cookieSpec(CookieSpecs.IGNORE_COOKIES); - - MockServer.expectCookie("JSESSIONID", "ABC123"); - Unirest.get(MockServer.GET).asEmpty(); - Unirest.get(MockServer.GET) + void doesNotImpactPathParams() { + Unirest.get(MockServer.GET + "/{first param}/passed/{file name}") + .routeParam("first param", "foo") + .routeParam("file name", "Moody Blues") .asObject(RequestCapture.class) .getBody() - .assertNoCookie("JSESSIONID"); + .assertUrl("http://localhost:4567/get/foo/passed/Moody%20Blues"); } } diff --git a/unirest/src/test/java/BehaviorTests/RedirectHandlingTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/RedirectHandlingTest.java similarity index 68% rename from unirest/src/test/java/BehaviorTests/RedirectHandlingTest.java rename to unirest-bdd-tests/src/test/java/BehaviorTests/RedirectHandlingTest.java index b9f21a4b7..a08f8d40d 100644 --- a/unirest/src/test/java/BehaviorTests/RedirectHandlingTest.java +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/RedirectHandlingTest.java @@ -25,18 +25,18 @@ package BehaviorTests; -import kong.unirest.HttpResponse; -import kong.unirest.Unirest; -import org.junit.Test; +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.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; -public class RedirectHandlingTest extends BddTest { +class RedirectHandlingTest extends BddTest { @Test - public void willFollowRedirectsByDefault() { + void willFollowRedirectsByDefault() { Unirest.get(MockServer.REDIRECT) .asObject(RequestCapture.class) .getBody() @@ -44,15 +44,24 @@ public void willFollowRedirectsByDefault() { } @Test - public void canDisableRedirects(){ + 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); - HttpResponse response = Unirest.get(MockServer.REDIRECT).asEmpty(); + var response = Unirest.get(MockServer.REDIRECT).asEmpty(); assertEquals(301, response.getStatus()); } @Test - public void willFollowRedirectsByDefaultAsync() throws ExecutionException, InterruptedException { + void willFollowRedirectsByDefaultAsync() throws ExecutionException, InterruptedException { Unirest.get(MockServer.REDIRECT) .asObjectAsync(RequestCapture.class) .get() @@ -61,9 +70,9 @@ public void willFollowRedirectsByDefaultAsync() throws ExecutionException, Inter } @Test - public void canDisableRedirectsAsync() throws Exception { + void canDisableRedirectsAsync() throws Exception { Unirest.config().followRedirects(false); - HttpResponse response = Unirest.get(MockServer.REDIRECT).asEmptyAsync().get(); + var response = Unirest.get(MockServer.REDIRECT).asEmptyAsync().get(); assertEquals(301, response.getStatus()); } diff --git a/unirest/src/test/java/BehaviorTests/RequestCapture.java b/unirest-bdd-tests/src/test/java/BehaviorTests/RequestCapture.java similarity index 51% rename from unirest/src/test/java/BehaviorTests/RequestCapture.java rename to unirest-bdd-tests/src/test/java/BehaviorTests/RequestCapture.java index bf2194634..ca9ca5741 100644 --- a/unirest/src/test/java/BehaviorTests/RequestCapture.java +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/RequestCapture.java @@ -26,31 +26,37 @@ package BehaviorTests; import com.fasterxml.jackson.annotation.JsonIgnore; -import com.google.common.base.Splitter; import com.google.common.base.Strings; -import com.google.common.collect.*; -import kong.unirest.*; -import org.apache.http.client.utils.URLEncodedUtils; -import spark.Request; - -import javax.servlet.MultipartConfigElement; -import javax.servlet.ServletException; -import javax.servlet.http.Cookie; -import javax.servlet.http.Part; +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 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 kong.unirest.JsonPatchRequest.CONTENT_TYPE; import static java.lang.System.getProperty; -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; +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 ListMultimap headers = LinkedListMultimap.create(); - public List files = new ArrayList<>(); + 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; @@ -64,26 +70,32 @@ public class RequestCapture { public HashMap cookies = new HashMap<>(); - public RequestCapture() { + public RequestCapture(){ + } - public RequestCapture(Request req) { + public RequestCapture(Context req) { url = req.url(); queryString = req.queryString(); - method = HttpMethod.valueOf(req.requestMethod()); - writeHeaders(req); + method = HttpMethod.valueOf(req.method().name()); + headers = new HeaderAsserts(req); writeQuery(req); populateParams(req); - cookies.putAll(req.cookies()); + cookies.putAll(req.cookieMap()); contentType = req.contentType(); status = 200; } - private void populateParams(Request req) { - routeParams.putAll(req.params()); + 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(Request req) { + public void writeBody(Context req) { if (Strings.nullToEmpty(req.contentType()).equals(CONTENT_TYPE)) { String body = req.body(); jsonPatches = new JsonPatch(body); @@ -95,22 +107,21 @@ public void writeBody(Request req) { } private void parseBodyToFormParams() { - URLEncodedUtils.parse(this.body, Charset.forName("UTF-8")) - .forEach(p -> { - params.put(p.getName(), p.getValue()); - }); + try { + QueryParams.fromBody(this.body) + .getQueryParams() + .forEach(p -> { + params.put(p.getName(), p.getValue()); + }); + }catch (UnirestException e){} } - public void writeMultipart(Request req) { - req.raw().setAttribute("org.eclipse.jetty.multipartConfig", new MultipartConfigElement(getProperty("java.io.tmpdir"))); + public void writeMultipart(Context req) { + req.req().setAttribute("org.eclipse.jetty.multipartConfig", new MultipartConfigElement(getProperty("java.io.tmpdir"))); try { - for (Part p : req.raw().getParts()) { - if (!Strings.isNullOrEmpty(p.getSubmittedFileName())) { - buildFilePart(p); - } else { - buildUrlEncodedParamPart(p); - } + for (Part p : req.req().getParts()) { + buildMultiContentPart(p); } } catch (ServletException e) { this.body = req.body(); @@ -121,23 +132,22 @@ public void writeMultipart(Request req) { } } - private void buildUrlEncodedParamPart(Part p) throws IOException { - java.util.Scanner s = new Scanner(p.getInputStream()).useDelimiter("\\A"); - String value = s.hasNext() ? s.next() : ""; - params.put(p.getName(), value); - } - - public void buildFilePart(Part part) throws IOException { - FormPart file = new FormPart(); + public void buildMultiContentPart(Part part) throws IOException { + MultiPart file = new MultiPart(); file.fileName = part.getSubmittedFileName(); file.type = part.getContentType(); - file.inputName = part.getName(); + file.name = part.getName(); file.fileType = part.getContentType(); file.size = part.getSize(); - file.body = TestUtil.toString(part.getInputStream()); + 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); + } - files.add(file); + multiformparts.add(file); } private ListMultimap extractHeaders(Part part) { @@ -148,37 +158,27 @@ private ListMultimap extractHeaders(Part part) { return h; } - private void writeQuery(Request req) { - req.queryParams().forEach(q -> params.putAll(q, Sets.newHashSet(req.queryMap(q).values()))); - } - - public RequestCapture asserBody(String s) { - assertEquals(s, body); - return this; + private void writeQuery(Context req) { + req.queryParamMap().forEach((key, value) -> params.putAll(key, value)); } public RequestCapture assertNoHeader(String s) { - assertFalse("Should Have No Header " + s, headers.containsKey(s)); + headers.assertNoHeader(s); return this; } - private RequestCapture writeHeaders(Request req) { - req.headers().forEach(h -> headers.putAll(h, Collections.list(req.raw().getHeaders(h)))); - return this; - } - - public RequestCapture assertHeader(String key, String value) { - assertTrue(String.format("Expect header of '%s' but none was present", key), headers.containsKey(key)); - assertThat("Expected Header Value Failed", headers.get(key), hasItem(value)); + public RequestCapture assertHeader(String key, String... value) { + headers.assertHeader(key, value); return this; } public RequestCapture assertParam(String key, String value) { - assertThat("Expected Query or Form value", params.get(key), hasItem(value)); + assertTrue(params.containsKey(key), String.format("Expect param of '%s' but none was present", key)); + assertTrue(params.get(key).contains(value), "Expected Query or Form value: " + value); return this; } - public FormPart getFile(String fileName) { + public MultiPart getFile(String fileName) { return getFileStream() .filter(f -> Objects.equals(f.fileName, fileName)) .findFirst() @@ -186,21 +186,21 @@ public FormPart getFile(String fileName) { + "Found: " + getFileStream().map(f -> f.fileName).collect(Collectors.joining(" ")))); } - private Stream getFileStream() { - return files.stream() + private Stream getFileStream() { + return multiformparts.stream() .filter(f -> f.isFile()); } - public FormPart getFileByInput(String input) { + public MultiPart getFileByInput(String input) { return getFileStream() - .filter(f -> Objects.equals(f.inputName, input)) + .filter(f -> Objects.equals(f.name, input)) .findFirst() .orElseThrow(() -> new RuntimeException("No File from form: " + input)); } - public List getAllFilesByInput(String input) { + public List getAllFilesByInput(String input) { return getFileStream() - .filter(f -> Objects.equals(f.inputName, input)) + .filter(f -> Objects.equals(f.name, input)) .collect(Collectors.toList()); } @@ -210,8 +210,7 @@ public RequestCapture assertFileContent(String input, String content) { } public RequestCapture assertBasicAuth(String username, String password) { - String raw = headers.get("Authorization").get(0); - TestUtil.assertBasicAuth(raw, username, password); + headers.assertBasicAuth(username, password); return this; } @@ -220,13 +219,13 @@ public RequestCapture assertQueryString(String s) { return this; } - public RequestCapture asserMethod(HttpMethod get) { + public RequestCapture assertMethod(HttpMethod get) { assertEquals(get, method); return this; } public RequestCapture assertPathParam(String name, String value) { - assertEquals(value, routeParams.get(":" + name)); + assertEquals(value, routeParams.get(name)); return this; } @@ -236,12 +235,12 @@ public RequestCapture assertUrl(String s) { } public void assertCharset(Charset charset) { - assertThat(contentType, endsWith(charset.toString())); + assertTrue(contentType.endsWith(charset.toString()), "Expected Content Type With Charset: " + charset); } public RequestCapture assertJsonPatch(JsonPatchOperation op, String path, Object value) { - assertNotNull("Asserting JSONPatch but no patch object present", jsonPatches); - assertThat(jsonPatches.getOperations(), hasItem(new JsonPatchItem(op, path, value))); + assertNotNull(jsonPatches, "Asserting JSONPatch but no patch object present"); + assertTrue(jsonPatches.getOperations().contains(new JsonPatchItem(op, path, value))); return this; } @@ -263,54 +262,86 @@ public RequestCapture assertIsProxied(boolean b) { return this; } - public RequestCapture assertHeaderSize(String foo, int size) { - assertEquals(size, headers.get(foo).size()); + public RequestCapture assertHeaderSize(String name, int size) { + headers.assertHeaderSize(name, size); return this; } - public void assertBody(String o) { + 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() { - List h = headers.get("Content-Type"); - assertEquals("Expected exactly 1 Content-Type header", 1, h.size()); - List parts = Splitter.on(";").trimResults().splitToList(h.get(0)); - assertEquals("multipart/form-data", parts.get(0)); - assertThat(parts.get(1), startsWith("boundary=")); - assertEquals("charset=UTF-8", parts.get(2)); + headers.assertMultiPartContentType(); return this; } public RequestCapture assertUrlEncodedContent() { - return assertContentType("application/x-www-form-urlencoded; charset=UTF-8"); + return assertRawContentType("application/x-www-form-urlencoded; charset=UTF-8"); } - public void assertCookie(String name, String value) { + public RequestCapture assertCookie(String name, String value) { String c = cookies.get(name); - assertNotNull("expected a cookie to be passed to the server but got none. Name: " + name, c); + assertNotNull(c, "expected a cookie to be passed to the server but got none. Name: " + name); assertEquals(value, c); + return this; } - public void assertNoCookie(String name) { - assertNull("Cookie should not have been passed but it was! ", cookies.get(name)); + 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 static class FormPart { - public String contentType; + 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 inputName; + public String name; public String body; public String fileType; public long size; @@ -321,31 +352,43 @@ public boolean isFile(){ return fileName != null; } - public FormPart assertBody(String content) { + public MultiPart assertBody(String content) { assertEquals(content, body); return this; } - public FormPart assertFileType(String type) { + public MultiPart assertFileType(String type) { assertEquals(type, this.fileType); return this; } - public FormPart assertFileType(ContentType imageJpeg) { + public MultiPart assertFileType(ContentType imageJpeg) { return assertFileType(imageJpeg.toString()); } - public FormPart assertFileName(String s) { + public MultiPart assertFileName(String s) { assertEquals(s, fileName); return this; } - public void assertSize(long expected) { + public MultiPart assertSize(long expected) { assertEquals(expected, this.size); + return this; } - public void exists() { + public MultiPart exists() { assertTrue(this.size > 0); + return this; + } + + public MultiPart assertContentType(String contentType) { + TestUtil.assertMultiMap(headers).contains(MapEntry.entry("content-type", contentType)); + return this; + } + + public MultiPart assertContentDisposition(String disposition) { + TestUtil.assertMultiMap(headers).contains(MapEntry.entry("content-disposition", disposition)); + return this; } } diff --git a/unirest/src/test/java/BehaviorTests/ResponseHeaderTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/ResponseHeaderTest.java similarity index 55% rename from unirest/src/test/java/BehaviorTests/ResponseHeaderTest.java rename to unirest-bdd-tests/src/test/java/BehaviorTests/ResponseHeaderTest.java index b0d19f106..a3fa30bac 100644 --- a/unirest/src/test/java/BehaviorTests/ResponseHeaderTest.java +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/ResponseHeaderTest.java @@ -25,40 +25,46 @@ package BehaviorTests; -import org.junit.Test; -import kong.unirest.Header; -import kong.unirest.Headers; -import kong.unirest.Unirest; +import com.google.common.collect.Lists; +import kong.unirest.core.Headers; +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertEquals; +import java.util.Arrays; +import java.util.List; -public class ResponseHeaderTest extends BddTest { +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +class ResponseHeaderTest extends BddTest { @Test - public void responseHeadersAreInTheSameOrderAsTheResponse() { + void readingResponseHeaders() { MockServer.addResponseHeader("zed", "oranges"); MockServer.addResponseHeader("alpha", "apples"); - MockServer.addResponseHeader("Content", "application/xml"); 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)); - assertHeader("Set-Cookie", "JSESSIONID=ABC123", h.all().get(1)); - assertHeader("Expires", "Thu, 01 Jan 1970 00:00:00 GMT", h.all().get(2)); - assertHeader("zed", "oranges", h.all().get(3)); - assertHeader("alpha", "apples", h.all().get(4)); - assertHeader("Content", "application/xml", h.all().get(5)); - assertHeader("zed", "grapes", h.all().get(6)); - assertHeader("Content-Type", "text/html;charset=utf-8", h.all().get(7)); - assertHeader("Transfer-Encoding", "chunked", h.all().get(8)); - + // 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")); } - private void assertHeader(String name, String value, Header header) { - assertEquals(name, header.getName()); - assertEquals(value, header.getValue()); + @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/src/test/java/BehaviorTests/ConsumerTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/SseRealExamples.java similarity index 59% rename from unirest/src/test/java/BehaviorTests/ConsumerTest.java rename to unirest-bdd-tests/src/test/java/BehaviorTests/SseRealExamples.java index 3c2d4a7aa..edd78dfb4 100644 --- a/unirest/src/test/java/BehaviorTests/ConsumerTest.java +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/SseRealExamples.java @@ -25,42 +25,30 @@ package BehaviorTests; -import org.junit.After; -import org.junit.Test; -import kong.unirest.Unirest; -import static org.junit.Assert.assertEquals; +import BehaviorTests.wikipedia.RecentChange; +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; -public class ConsumerTest extends BddTest { - - private int status; - - @Override - @After - public void tearDown() { - super.tearDown(); - status = 0; - } +@Disabled +public class SseRealExamples { @Test - public void canSimplyConsumeAResponse() { - Unirest.get(MockServer.GET) - .thenConsume(r -> status = r.getStatus()); - - assertEquals(200, status); + 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 - public 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); + 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/src/test/java/BehaviorTests/TimeoutTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/TimeoutTest.java similarity index 60% rename from unirest/src/test/java/BehaviorTests/TimeoutTest.java rename to unirest-bdd-tests/src/test/java/BehaviorTests/TimeoutTest.java index f1ead7328..aafaf9b4b 100644 --- a/unirest/src/test/java/BehaviorTests/TimeoutTest.java +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/TimeoutTest.java @@ -27,39 +27,64 @@ package BehaviorTests; -import kong.unirest.Config; -import kong.unirest.Unirest; -import kong.unirest.UnirestException; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.nio.client.HttpAsyncClientBuilder; -import org.junit.Ignore; -import org.junit.Test; - -import java.io.IOException; -import java.net.InetAddress; +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.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.*; -public class TimeoutTest extends BddTest { + +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 - public void testSetTimeouts() { + 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_CONNECTION_TIMEOUT + 100) { // Add 100ms for code execution + if (System.currentTimeMillis() - start > Config.DEFAULT_CONNECT_TIMEOUT + 100) { // Add 100ms for code execution fail(); } } Unirest.config().reset(); - Unirest.config().connectTimeout(2000).socketTimeout(10000); + Unirest.config().connectTimeout(2000); start = System.currentTimeMillis(); try { @@ -72,15 +97,15 @@ public void testSetTimeouts() { } @Test - @Ignore // this is flakey - public void parallelTest() throws InterruptedException { - Unirest.config().connectTimeout(10).socketTimeout(5); + @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).socketTimeout(20); + Unirest.config().connectTimeout(200); start = System.currentTimeMillis(); makeParallelRequests(); long higherConcurrencyTime = (System.currentTimeMillis() - start); @@ -104,46 +129,4 @@ private void makeParallelRequests() throws InterruptedException { newFixedThreadPool.shutdown(); newFixedThreadPool.awaitTermination(10, TimeUnit.MINUTES); } - - @Test - public void setTimeoutsAndCustomClient() { - try { - Unirest.config().connectTimeout(1000).socketTimeout(2000); - } catch (Exception e) { - fail(); - } - - try { - Unirest.config().asyncClient(HttpAsyncClientBuilder.create().build()); - } catch (Exception e) { - fail(); - } - - try { - Unirest.config().asyncClient(HttpAsyncClientBuilder.create().build()); - Unirest.config().connectTimeout(1000).socketTimeout(2000); - fail(); - } catch (Exception e) { - // Ok - } - - try { - Unirest.config().httpClient(HttpClientBuilder.create().build()); - Unirest.config().connectTimeout(1000).socketTimeout(2000); - fail(); - } catch (Exception e) { - // Ok - } - } - - private String findAvailableIpAddress() throws 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"); - } } 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..ef10e4b61 --- /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/src/test/java/BehaviorTests/VerbTest.java b/unirest-bdd-tests/src/test/java/BehaviorTests/VerbTest.java similarity index 66% rename from unirest/src/test/java/BehaviorTests/VerbTest.java rename to unirest-bdd-tests/src/test/java/BehaviorTests/VerbTest.java index c427e7522..c8bc08e3d 100644 --- a/unirest/src/test/java/BehaviorTests/VerbTest.java +++ b/unirest-bdd-tests/src/test/java/BehaviorTests/VerbTest.java @@ -25,68 +25,78 @@ package BehaviorTests; -import kong.unirest.HttpMethod; -import kong.unirest.HttpResponse; -import kong.unirest.Unirest; -import org.junit.Test; +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.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class VerbTest extends BddTest { -public class VerbTest extends BddTest { @Test - public void get() { + void get() { Unirest.get(MockServer.GET) .asObject(RequestCapture.class) .getBody() - .asserMethod(HttpMethod.GET); + .assertMethod(HttpMethod.GET); } @Test - public void post() { + void post() { Unirest.post(MockServer.POST) .asObject(RequestCapture.class) .getBody() - .asserMethod(HttpMethod.POST); + .assertMethod(HttpMethod.POST); } @Test - public void put() { + void put() { Unirest.put(MockServer.POST) .asObject(RequestCapture.class) .getBody() - .asserMethod(HttpMethod.PUT); + .assertMethod(HttpMethod.PUT); } @Test - public void patch() { + void patch() { Unirest.patch(MockServer.PATCH) .asObject(RequestCapture.class) .getBody() - .asserMethod(HttpMethod.PATCH); + .assertMethod(HttpMethod.PATCH); } @Test - public void head() { - HttpResponse response = Unirest.head(MockServer.GET).asEmpty(); + void head() { + var response = Unirest.head(MockServer.GET).asEmpty(); assertEquals(200, response.getStatus()); - assertEquals("text/html;charset=utf-8", response.getHeaders().getFirst("Content-Type")); + assertEquals("text/plain;charset=utf-8", response.getHeaders().getFirst("Content-Type")); } @Test - public void option() { + void option() { Unirest.options(MockServer.GET) .asObject(RequestCapture.class) .getBody() - .asserMethod(HttpMethod.OPTIONS); + .assertMethod(HttpMethod.OPTIONS); } @Test - public void weirdVerbs() { + void weirdVerbs() { Unirest.request("CHEESE", MockServer.CHEESE) .asObject(RequestCapture.class) .getBody() - .asserMethod(HttpMethod.valueOf("CHEESE")) + .assertMethod(HttpMethod.valueOf("INVALID")) .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/src/main/java/kong/unirest/ObjectMapper.java b/unirest-bdd-tests/src/test/java/kong/unirest/core/Clock.java similarity index 81% rename from unirest/src/main/java/kong/unirest/ObjectMapper.java rename to unirest-bdd-tests/src/test/java/kong/unirest/core/Clock.java index 3e86d1c3f..3a75ddd18 100644 --- a/unirest/src/main/java/kong/unirest/ObjectMapper.java +++ b/unirest-bdd-tests/src/test/java/kong/unirest/core/Clock.java @@ -23,12 +23,19 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +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(); + } -public interface ObjectMapper { - T readValue(String value, Class valueType); - default T readValue(String value, GenericType genericType){ - throw new UnirestException("Please implement me"); - } - String writeValue(Object value); } 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..1548e4c6d --- /dev/null +++ b/unirest-bdd-tests/src/test/java/kong/unirest/core/CookieParsingTest.java @@ -0,0 +1,162 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF 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.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 matchJettyParsing() { + String s = "_id=A528A0D64DA61CB01241EF6E18E4D675170DDB56CB430000AF58625E920CF940~pl1ZYwDDdoAnfY3RtqZ2Ti1MYOf7Q8jRrFQLneSGK7qQoUX1GHW/xDcqweGyclm5rm/g/YFCV3ohuHoz2oad5M0MX9Ru9V7bFr2s08d1lHxbn39gw71AI+ZVejq5FpMHKBzyjoBGG6NY6xYVTwP9NHo14SY0CXs60k2UTpJsOTNzAHIZaedg7o6R/8qyAQ8GF25K2o773pFLrYtjgKHohkk5ukz/yEGQitq8NgC5hiqX0=; expires=Fri, 06 Mar 2020 16:05:35 GMT; max-age=7200; path=/; domain=xxx.com; HttpOnly"; + var cutter = new org.eclipse.jetty.server.Cookies(); + cutter.addCookieField(s); + jakarta.servlet.http.Cookie jetty = cutter.getCookies()[0]; + + Cookie unirest = new Cookie(s); + + assertEquals(jetty.getValue(), unirest.getValue()); + } + + @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/src/test/java/kong/unirest/apache/UtilTest.java b/unirest-bdd-tests/src/test/java/kong/unirest/core/EventSerializationTest.java similarity index 55% rename from unirest/src/test/java/kong/unirest/apache/UtilTest.java rename to unirest-bdd-tests/src/test/java/kong/unirest/core/EventSerializationTest.java index 6e603b121..f91f44c37 100644 --- a/unirest/src/test/java/kong/unirest/apache/UtilTest.java +++ b/unirest-bdd-tests/src/test/java/kong/unirest/core/EventSerializationTest.java @@ -23,35 +23,41 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest.apache; +package kong.unirest.core; -import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; -import org.apache.http.impl.nio.client.HttpAsyncClientBuilder; -import org.apache.http.nio.client.HttpAsyncClient; -import org.junit.Test; +import BehaviorTests.Foo; +import BehaviorTests.JacksonObjectMapper; +import kong.unirest.core.java.Event; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import java.util.List; -public class UtilTest { +import static org.assertj.core.api.Assertions.assertThat; +public class EventSerializationTest { @Test - public void canCast() { - Object foo = new Foo(){}; + void canDeserialize() { + var config = new Config(); + var om = new JacksonObjectMapper(); + config.setObjectMapper(om); - assertEquals(foo, Util.tryCast(foo, Foo.class).get()); - assertEquals(false, Util.tryCast("foo", Foo.class).isPresent()); - assertEquals(false, Util.tryCast(null, Foo.class).isPresent()); - assertEquals(true, Util.tryCast(new Bar(), Foo.class).isPresent()); + var foo = new Foo("qux"); + var event = new Event("", "message", om.writeValue(foo), config); + + assertThat(event.asObject(Foo.class)) + .isEqualTo(foo); } @Test - public void canCastAAsyncClient() { - HttpAsyncClient build = HttpAsyncClientBuilder.create().build(); - - assertEquals(true, Util.tryCast(build, CloseableHttpAsyncClient.class).isPresent()); - } + void canDeserializeWithGenericTypes(){ + var config = new Config(); + var om = new JacksonObjectMapper(); + config.setObjectMapper(om); - public abstract class Foo {} + var foo = List.of(new Foo("qux")); + var event = new Event("", "message", om.writeValue(foo), config); - public class Bar extends Foo {} -} \ No newline at end of file + assertThat(event.asObject(new GenericType>() {})) + .isEqualTo(foo); + } +} diff --git a/unirest/src/test/java/kong/unirest/JsonNodeTest.java b/unirest-bdd-tests/src/test/java/kong/unirest/core/JsonNodeTest.java similarity index 82% rename from unirest/src/test/java/kong/unirest/JsonNodeTest.java rename to unirest-bdd-tests/src/test/java/kong/unirest/core/JsonNodeTest.java index b17fea8b0..bb8247b1d 100644 --- a/unirest/src/test/java/kong/unirest/JsonNodeTest.java +++ b/unirest-bdd-tests/src/test/java/kong/unirest/core/JsonNodeTest.java @@ -23,36 +23,38 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; -import org.junit.Test; -import static org.junit.Assert.*; +import kong.unirest.core.JsonNode; +import org.junit.jupiter.api.Test; -public class JsonNodeTest { +import static org.junit.jupiter.api.Assertions.*; + +class JsonNodeTest { @Test - public void canParseARegularObject() { + void canParseARegularObject() { String json = "{\"foo\":\"bar\"}"; JsonNode node = new JsonNode(json); - assertEquals(false, node.isArray()); + assertFalse(node.isArray()); assertEquals("bar", node.getObject().getString("foo")); assertEquals("bar", node.getArray().getJSONObject(0).getString("foo")); assertEquals(json, node.toString()); } @Test - public void canParseArrayObject() { + void canParseArrayObject() { String json = "[{\"foo\":\"bar\"}]"; JsonNode node = new JsonNode(json); - assertEquals(true, node.isArray()); + assertTrue(node.isArray()); assertEquals("bar", node.getArray().getJSONObject(0).getString("foo")); - assertEquals(null, node.getObject()); + assertNull(node.getObject()); assertEquals(json, node.toString()); } @Test - public void nullAndEmptyObjectsResultInEmptyJson() { + void nullAndEmptyObjectsResultInEmptyJson() { assertEquals("{}", new JsonNode("").toString()); assertEquals("{}", new JsonNode(null).toString()); } diff --git a/unirest/src/test/java/kong/unirest/JsonPatchItemTest.java b/unirest-bdd-tests/src/test/java/kong/unirest/core/JsonPatchItemTest.java similarity index 81% rename from unirest/src/test/java/kong/unirest/JsonPatchItemTest.java rename to unirest-bdd-tests/src/test/java/kong/unirest/core/JsonPatchItemTest.java index 9047f0509..1a2cce850 100644 --- a/unirest/src/test/java/kong/unirest/JsonPatchItemTest.java +++ b/unirest-bdd-tests/src/test/java/kong/unirest/core/JsonPatchItemTest.java @@ -23,20 +23,21 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; import com.google.common.collect.Sets; -import org.junit.Test; +import kong.unirest.core.JsonPatchItem; +import org.junit.jupiter.api.Test; import java.util.Set; -import static kong.unirest.JsonPatchOperation.*; -import static org.junit.Assert.*; +import static kong.unirest.core.JsonPatchOperation.*; +import static org.junit.jupiter.api.Assertions.*; -public class JsonPatchItemTest { +class JsonPatchItemTest { @Test - public void patchOperationsAreUniqueByTheirElements() { + void patchOperationsAreUniqueByTheirElements() { assertEquals( new JsonPatchItem(add, "/foo", "bar"), new JsonPatchItem(add, "/foo", "bar") @@ -64,23 +65,23 @@ public void patchOperationsAreUniqueByTheirElements() { } @Test - public void testHash() { - Set s = Sets.newHashSet(new JsonPatchItem(add, "/foo", "bar"), + void testHash() { + Set s = Sets.newHashSet(new JsonPatchItem(add, "/foo", "bar"), new JsonPatchItem(add, "/foo", "bar")); assertEquals(1, s.size()); } @Test - public void edgeEquals() { + void edgeEquals() { JsonPatchItem i = new JsonPatchItem(add, "/foo", "bar"); - assertTrue(i.equals(i)); - assertFalse(i.equals(null)); - assertFalse(i.equals(new Object())); + assertEquals(i, i); + assertNotEquals(null, i); + assertNotEquals(new Object(), i); } @Test - public void basicTest() { + void basicTest() { JsonPatchItem i = new JsonPatchItem(add, "/foo", "bar"); assertEquals(add, i.getOp()); assertEquals("/foo", i.getPath()); 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/unirest/src/test/resources/image.jpg b/unirest-bdd-tests/src/test/resources/image.jpg similarity index 100% rename from unirest/src/test/resources/image.jpg rename to unirest-bdd-tests/src/test/resources/image.jpg diff --git a/unirest/src/test/resources/data/cp1250.txt b/unirest-bdd-tests/src/test/resources/public/data/cp1250.txt similarity index 100% rename from unirest/src/test/resources/data/cp1250.txt rename to unirest-bdd-tests/src/test/resources/public/data/cp1250.txt diff --git a/unirest/src/test/resources/rawPost.txt b/unirest-bdd-tests/src/test/resources/rawPost.txt similarity index 53% rename from unirest/src/test/resources/rawPost.txt rename to unirest-bdd-tests/src/test/resources/rawPost.txt index ef3657d10..42b11480a 100644 --- a/unirest/src/test/resources/rawPost.txt +++ b/unirest-bdd-tests/src/test/resources/rawPost.txt @@ -1,14 +1,16 @@ --IDENTIFIER -Content-Disposition: form-data; name="file"; filename="test" -Content-Type: application/octet-stream +Content-Disposition: form-data; name="marky" +Content-Type: application/x-www-form-urlencoded; charset=UTF-8 -This is a test file +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="marky" +Content-Disposition: form-data; name="file"; filename="test.txt" +Content-Type: application/octet-stream -mark +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/src/test/resources/spidey.jpg b/unirest-bdd-tests/src/test/resources/spidey.jpg similarity index 100% rename from unirest/src/test/resources/spidey.jpg rename to unirest-bdd-tests/src/test/resources/spidey.jpg 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/unirest/src/test/resources/test b/unirest-bdd-tests/src/test/resources/test.txt similarity index 100% rename from unirest/src/test/resources/test rename to unirest-bdd-tests/src/test/resources/test.txt diff --git a/unirest/src/test/resources/test2 b/unirest-bdd-tests/src/test/resources/test2.txt similarity index 100% rename from unirest/src/test/resources/test2 rename to unirest-bdd-tests/src/test/resources/test2.txt diff --git a/unirest-java-bom/pom.xml b/unirest-java-bom/pom.xml new file mode 100644 index 000000000..0d87553f8 --- /dev/null +++ b/unirest-java-bom/pom.xml @@ -0,0 +1,49 @@ + + + + unirest-java-parent + com.konghq + 4.8.2-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/object-mapper-gson/README.md b/unirest-modules-gson/README.md similarity index 100% rename from object-mapper-gson/README.md rename to unirest-modules-gson/README.md diff --git a/object-mapper-gson/pom.xml b/unirest-modules-gson/pom.xml similarity index 62% rename from object-mapper-gson/pom.xml rename to unirest-modules-gson/pom.xml index c83f447a5..7522ec642 100644 --- a/object-mapper-gson/pom.xml +++ b/unirest-modules-gson/pom.xml @@ -5,11 +5,11 @@ com.konghq unirest-java-parent - 2.3.08-SNAPSHOT + 4.8.2-SNAPSHOT - unirest-object-mappers-gson - unirest-objectmappers-gson + unirest-modules-gson + unirest-modules-gson Gson based object mapper for Unirest jar @@ -20,25 +20,14 @@ com.konghq - unirest-java + unirest-java-core ${project.version} provided com.google.code.gson gson - 2.8.5 + 2.13.2 - - - - - - org.apache.maven.plugins - maven-shade-plugin - - - - \ 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/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/object-mapper-gson/src/test/java/kong/unirest/GsonObjectMapperTest.java b/unirest-modules-gson/src/test/java/kong/unirest/modules/gson/GsonObjectMapperTest.java similarity index 73% rename from object-mapper-gson/src/test/java/kong/unirest/GsonObjectMapperTest.java rename to unirest-modules-gson/src/test/java/kong/unirest/modules/gson/GsonObjectMapperTest.java index e7039bf3a..738075429 100644 --- a/object-mapper-gson/src/test/java/kong/unirest/GsonObjectMapperTest.java +++ b/unirest-modules-gson/src/test/java/kong/unirest/modules/gson/GsonObjectMapperTest.java @@ -47,22 +47,24 @@ a copy of this software and associated documentation files (the OF 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; +package kong.unirest.modules.gson; -import org.junit.Test; +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.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -public class GsonObjectMapperTest { +class GsonObjectMapperTest { private GsonObjectMapper om = new GsonObjectMapper(); @Test - public void canWrite() { - TestMe test = new TestMe("foo", 42, new TestMe("bar", 666, null)); + void canWrite() throws Exception { + var test = new TestMe("foo", 42, new TestMe("bar", 666, null)); String json = om.writeValue(test); @@ -74,35 +76,47 @@ public void canWrite() { } @Test - public void canRead(){ - TestMe test = om.readValue("{\"text\":\"foo\",\"nmbr\":42,\"another\":{\"text\":\"bar\",\"nmbr\":666}}", + 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); + assertEquals(42, test.nmbr.intValue()); assertEquals("bar", test.another.text); - assertEquals(666, test.another.nmbr); + assertEquals(666, test.another.nmbr.intValue()); assertEquals(null, test.another.another); } @Test - public void canReadGenerics(){ - List testList = om.readValue("[{\"text\":\"foo\",\"nmbr\":42,\"another\":{\"text\":\"bar\",\"nmbr\":666,\"another\":null}}]", + void canReadGenerics(){ + var testList = om.readValue("[{\"text\":\"foo\",\"nmbr\":42,\"another\":{\"text\":\"bar\",\"nmbr\":666,\"another\":null}}]", new GenericType>(){}); - TestMe test = testList.get(0); + var test = testList.get(0); assertEquals("foo", test.text); - assertEquals(42, test.nmbr); + assertEquals(42, test.nmbr.intValue()); assertEquals("bar", test.another.text); - assertEquals(666, test.another.nmbr); + 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 int nmbr; + public Integer nmbr; public TestMe another; public TestMe(){} 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..305108144 --- /dev/null +++ b/unirest-modules-gson/src/test/java/kong/unirest/modules/gson/JSONArrayTest.java @@ -0,0 +1,556 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF 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.Foo; +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/object-mapper-jackson/pom.xml b/unirest-modules-jackson-legacy/pom.xml similarity index 54% rename from object-mapper-jackson/pom.xml rename to unirest-modules-jackson-legacy/pom.xml index 0613dd30f..c5814fffe 100644 --- a/object-mapper-jackson/pom.xml +++ b/unirest-modules-jackson-legacy/pom.xml @@ -1,44 +1,37 @@ 4.0.0 - com.konghq unirest-java-parent - 2.3.08-SNAPSHOT + 4.8.2-SNAPSHOT - unirest-objectmapper-jackson - unirest-objectmapper-jackson - Jackson based object mapper for Unirest + unirest-modules-jackson-legacy + unirest-modules-jackson-legacy + Jackson 2 based object mapper for Unirest - 2.9.9 ${project.parent.basedir} + 2.21.2 - + com.konghq - unirest-java + unirest-java-core ${project.version} provided com.fasterxml.jackson.core jackson-databind - ${jackson.version} + ${legacy-jackson.version} + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${legacy-jackson.version} - - - - - - org.apache.maven.plugins - maven-shade-plugin - - - - \ 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..c4d8912e7 --- /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 a 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/object-mapper-jackson/src/main/java/kong/unirest/JacksonObjectMapper.java b/unirest-modules-jackson-legacy/src/main/java/kong/unirest/modules/jackson2/JacksonObjectMapper.java similarity index 77% rename from object-mapper-jackson/src/main/java/kong/unirest/JacksonObjectMapper.java rename to unirest-modules-jackson-legacy/src/main/java/kong/unirest/modules/jackson2/JacksonObjectMapper.java index 94adb26a5..b97383168 100644 --- a/object-mapper-jackson/src/main/java/kong/unirest/JacksonObjectMapper.java +++ b/unirest-modules-jackson-legacy/src/main/java/kong/unirest/modules/jackson2/JacksonObjectMapper.java @@ -47,25 +47,48 @@ a copy of this software and associated documentation files (the OF 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; +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() { + 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 { 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/JSONArrayTest.java b/unirest-modules-jackson-legacy/src/test/java/kong/unirest/modules/jackson2/JSONArrayTest.java new file mode 100644 index 000000000..c7e9c4b09 --- /dev/null +++ b/unirest-modules-jackson-legacy/src/test/java/kong/unirest/modules/jackson2/JSONArrayTest.java @@ -0,0 +1,546 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF 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.Foo; +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/object-mapper-jackson/src/test/java/kong/unirest/JacksonObjectMapperTest.java b/unirest-modules-jackson-legacy/src/test/java/kong/unirest/modules/jackson2/JacksonObjectMapperTest.java similarity index 67% rename from object-mapper-jackson/src/test/java/kong/unirest/JacksonObjectMapperTest.java rename to unirest-modules-jackson-legacy/src/test/java/kong/unirest/modules/jackson2/JacksonObjectMapperTest.java index 0c0697e99..549514666 100644 --- a/object-mapper-jackson/src/test/java/kong/unirest/JacksonObjectMapperTest.java +++ b/unirest-modules-jackson-legacy/src/test/java/kong/unirest/modules/jackson2/JacksonObjectMapperTest.java @@ -47,21 +47,25 @@ a copy of this software and associated documentation files (the OF 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; +package kong.unirest.modules.jackson2; -import org.junit.Test; +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.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -public class JacksonObjectMapperTest { +class JacksonObjectMapperTest { private JacksonObjectMapper om = new JacksonObjectMapper(); @Test - public void canWrite() { - TestMe test = new TestMe("foo", 42, new TestMe("bar", 666, null)); + void canWrite() throws JSONException { + var test = new TestMe("foo", 42, new TestMe("bar", 666, null)); String json = om.writeValue(test); @@ -73,8 +77,8 @@ public void canWrite() { } @Test - public void canRead(){ - TestMe test = om.readValue("{\"text\":\"foo\",\"nmbr\":42,\"another\":{\"text\":\"bar\",\"nmbr\":666,\"another\":null}}", + void canRead(){ + var test = om.readValue("{\"text\":\"foo\",\"nmbr\":42,\"another\":{\"text\":\"bar\",\"nmbr\":666,\"another\":null}}", TestMe.class); assertEquals("foo", test.text); @@ -85,11 +89,11 @@ public void canRead(){ } @Test - public void canReadGenerics(){ - List testList = om.readValue("[{\"text\":\"foo\",\"nmbr\":42,\"another\":{\"text\":\"bar\",\"nmbr\":666,\"another\":null}}]", + void canReadGenerics(){ + var testList = om.readValue("[{\"text\":\"foo\",\"nmbr\":42,\"another\":{\"text\":\"bar\",\"nmbr\":666,\"another\":null}}]", new GenericType>(){}); - TestMe test = testList.get(0); + var test = testList.get(0); assertEquals("foo", test.text); assertEquals(42, test.nmbr); @@ -98,6 +102,21 @@ public void canReadGenerics(){ 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; 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/object-mapper-jackson/README.md b/unirest-modules-jackson/README.md similarity index 84% rename from object-mapper-jackson/README.md rename to unirest-modules-jackson/README.md index 69a61654a..4616c044e 100644 --- a/object-mapper-jackson/README.md +++ b/unirest-modules-jackson/README.md @@ -7,7 +7,7 @@ Use it like this: Unirest.config().setObjectMapper(new JacksonObjectMapper()); ``` -You may also provide it with your own com.fasterxml.jackson.databind.ObjectMapper. +You may also provide it with your own tools.jackson.databind.ObjectMapper. ## Install With [Maven](https://mvnrepository.com/artifact/com.konghq/) ``` @@ -16,4 +16,5 @@ You may also provide it with your own com.fasterxml.jackson.databind.ObjectMappe unirest-objectmapper-jackson 3.0.01 -``` \ No newline at end of file +``` + diff --git a/unirest-modules-jackson/pom.xml b/unirest-modules-jackson/pom.xml new file mode 100644 index 000000000..44333a744 --- /dev/null +++ b/unirest-modules-jackson/pom.xml @@ -0,0 +1,34 @@ + + + 4.0.0 + + + com.konghq + unirest-java-parent + 4.8.2-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..09028a6af --- /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 a 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/object-mapper-gson/src/main/java/kong/unirest/GsonObjectMapper.java b/unirest-modules-jackson/src/main/java/kong/unirest/modules/jackson/JacksonObjectMapper.java similarity index 58% rename from object-mapper-gson/src/main/java/kong/unirest/GsonObjectMapper.java rename to unirest-modules-jackson/src/main/java/kong/unirest/modules/jackson/JacksonObjectMapper.java index 52c09de35..f55d11198 100644 --- a/object-mapper-gson/src/main/java/kong/unirest/GsonObjectMapper.java +++ b/unirest-modules-jackson/src/main/java/kong/unirest/modules/jackson/JacksonObjectMapper.java @@ -47,33 +47,70 @@ a copy of this software and associated documentation files (the OF 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; +package kong.unirest.modules.jackson; -import com.google.gson.Gson; +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; -public class GsonObjectMapper implements ObjectMapper { - private Gson om; +import java.util.function.Consumer; - public GsonObjectMapper() { - this(new Gson()); + +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 GsonObjectMapper(Gson om) { + 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) { - return om.fromJson(value, valueType); + try { + return om.readValue(value, valueType); + } catch (JacksonException e) { + throw new UnirestException(e); + } } @Override public T readValue(String value, GenericType genericType) { - return om.fromJson(value, genericType.getType()); + try { + return om.readValue(value, om.constructType(genericType.getType())); + } catch (JacksonException e) { + throw new UnirestException(e); + } } @Override public String writeValue(Object value) { - return om.toJson(value); + try { + return om.writeValueAsString(value); + } catch (JacksonException e) { + throw new UnirestException(e); + } } } diff --git a/unirest/src/main/java/kong/unirest/apache/ApacheRequestWithBody.java b/unirest-modules-jackson/src/main/java/kong/unirest/modules/jackson/JacksonPrimitive.java similarity index 73% rename from unirest/src/main/java/kong/unirest/apache/ApacheRequestWithBody.java rename to unirest-modules-jackson/src/main/java/kong/unirest/modules/jackson/JacksonPrimitive.java index 65631b7df..c4953ecf7 100644 --- a/unirest/src/main/java/kong/unirest/apache/ApacheRequestWithBody.java +++ b/unirest-modules-jackson/src/main/java/kong/unirest/modules/jackson/JacksonPrimitive.java @@ -23,22 +23,23 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest.apache; +package kong.unirest.modules.jackson; -import kong.unirest.HttpMethod; -import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; +import tools.jackson.databind.node.ValueNode; +import kong.unirest.core.json.JsonEngine; -import java.net.URI; - -class ApacheRequestWithBody extends HttpEntityEnclosingRequestBase { - private HttpMethod method; +class JacksonPrimitive extends JacksonElement implements JsonEngine.Primitive { + public JacksonPrimitive(ValueNode element) { + super(element); + } - public ApacheRequestWithBody(HttpMethod method, String uri){ - this.method = method; - setURI(URI.create(uri)); + @Override + public boolean isBoolean() { + return element.isBoolean(); } + @Override - public String getMethod() { - return method.name(); + 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/JSONArrayTest.java b/unirest-modules-jackson/src/test/java/kong/unirest/modules/jackson/JSONArrayTest.java new file mode 100644 index 000000000..57fd29763 --- /dev/null +++ b/unirest-modules-jackson/src/test/java/kong/unirest/modules/jackson/JSONArrayTest.java @@ -0,0 +1,546 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF 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.Foo; +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..74b583752 --- /dev/null +++ b/unirest-modules-mocks/pom.xml @@ -0,0 +1,32 @@ + + + + com.konghq + unirest-java-parent + 4.8.2-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..738222858 --- /dev/null +++ b/unirest-modules-mocks/src/main/java/kong/unirest/core/EqualsBodyMatcher.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.List; +import java.util.Objects; + +public class EqualsBodyMatcher implements BodyMatcher { + private final String expected; + + public EqualsBodyMatcher(String body) { + this.expected = body; + } + + @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..1e6325caa --- /dev/null +++ b/unirest-modules-mocks/src/main/java/kong/unirest/core/Expectation.java @@ -0,0 +1,117 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF 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; + +/** + * A expectation for a particular method/path + */ +public interface Expectation { + /** + * A expected header for a request + * @param key the header key + * @param value the header value + * @return this Expectation + */ + Expectation header(String key, String value); + + /** + * A expected header for a request + * @param key the query key + * @param value the query value + * @return this Expectation + */ + Expectation queryString(String key, String value); + + /** + * A expected body for a request + * @param body the expected body + * @return this Expectation + */ + Expectation body(String body); + + /** + * A matcher for the body for a request + * @param matcher the matcher + * @return this Expectation + */ + Expectation body(BodyMatcher matcher); + + /** + * expect a null response + * @return The ExpectedResponse + */ + ExpectedResponse thenReturn(); + + /** + * expect a string response + * @param body the expected response body + * @return The ExpectedResponse + */ + ExpectedResponse thenReturn(String body); + + /** + * expect a json response + * @param jsonObject the expected response body + * @return The ExpectedResponse + */ + ExpectedResponse thenReturn(JSONElement jsonObject); + + /** + * expect a object response as defined by a pojo using the requests / configuration object mapper + * @param pojo the expected response body + * @return The ExpectedResponse + */ + ExpectedResponse thenReturn(Object pojo); + + /** + * A supplier for the expected body which will get invoked at the time of build the response. + * @param supplier the expected response body supplier + * @return The ExpectedResponse + */ + ExpectedResponse thenReturn(Supplier supplier); + + /** + * Allows for a full override of the way a expected response is built. + * useful in building more complicated test-doubles of services that implement logic + * @param fun the function to convert a request to a response + */ + void thenReturn(Function, ExpectedResponse> fun); + + /** + * verify that all Expectations was fulfilled at least once. + * @throws UnirestAssertion when all expectations have not been fulfilled + */ + void verify(); + + void verify(Times times); + + Expectation times(Times never); +} 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..f2dfbd81d --- /dev/null +++ b/unirest-modules-mocks/src/main/java/kong/unirest/core/ExpectedResponse.java @@ -0,0 +1,112 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF 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; + +/** + * The expected response of a assertion. + * Contains things like response status, body and headers. + */ +public interface ExpectedResponse { + /** + * Create a independent expected response. useful for + * systems creating test-doubles rather than strict mocking + * @param status the response status. + * @return a new expected response with this status. + */ + static ExpectedResponse of(int status) { + return new ExpectedResponseRecord(null).withStatus(status); + } + + /** + * adds a header to the expected response + * @param key the header key + * @param key the header value + * @return this ExpectedResponse + */ + ExpectedResponse withHeader(String key, String value); + + /** + * adds a collection of headers to the expected response + * @param headers the headers + * @return This ExpectedResponse + */ + ExpectedResponse withHeaders(Headers headers); + + /** + * sets the status of the expected response + * @param httpStatus the http status code + * @return this ExpectedResponse + */ + ExpectedResponse withStatus(int httpStatus); + + /** + * sets the status of the expected response + * @param httpStatus the http status code + * @param statusMessage the status message + * @return this ExpectedResponse + */ + ExpectedResponse withStatus(int httpStatus, String statusMessage); + + /** + * expect a string response + * @param body the expected response body + * @return The ExpectedResponse + */ + ExpectedResponse thenReturn(String body); + + /** + * expect a json response + * @param jsonObject the expected response body + * @return The ExpectedResponse + */ + ExpectedResponse thenReturn(JSONElement jsonObject); + + /** + * expect a object response as defined by a pojo using the requests / configuration object mapper + * @param pojo the expected response body + * @return The ExpectedResponse + */ + ExpectedResponse thenReturn(Object pojo); + + /** + * A supplier for the expected body which will get invoked at the time of build the response. + * @param supplier the expected response body supplier + * @return The ExpectedResponse + */ + ExpectedResponse thenReturn(Supplier supplier); + + /** + * verify that all Expectations was fulfilled at least once. + * @throws UnirestAssertion when all expectations have not been fulfilled + */ + void verify(); + + 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..a4abf5b21 --- /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("A 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/src/main/java/kong/unirest/Client.java b/unirest-modules-mocks/src/main/java/kong/unirest/core/MatchStatus.java similarity index 63% rename from unirest/src/main/java/kong/unirest/Client.java rename to unirest-modules-mocks/src/main/java/kong/unirest/core/MatchStatus.java index 19a9c08ca..8281a2222 100644 --- a/unirest/src/main/java/kong/unirest/Client.java +++ b/unirest-modules-mocks/src/main/java/kong/unirest/core/MatchStatus.java @@ -23,30 +23,36 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; -import java.util.function.Function; -import java.util.stream.Stream; +/** + * Indicates a successful match and a description of the failure (if any) + */ +public class MatchStatus { + private final boolean isSuccess; + private final String description; -public interface Client { /** - * @return the underlying client if this instance is wrapping another library (like Apache). + * Contruct a Match status + * @param isSuccess indicates success + * @param description a failure description */ - Object getClient(); + public MatchStatus(boolean isSuccess, String description) { + this.isSuccess = isSuccess; + this.description = description; + } /** - * Make a request - * @param The type of the body - * @param request the prepared request object - * @param transformer the function to transform the response - * @return a HttpResponse with a transformed body + * @return Was it successful or not? */ - HttpResponse request(HttpRequest request, Function> transformer); + public boolean isSuccess(){ + return isSuccess; + } /** - * @return a stream of exceptions possibly thrown while closing all the things. + * @return a description of the failure (if any) or null */ - Stream close(); - - void registerShutdownHook(); + 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..b742bda22 --- /dev/null +++ b/unirest-modules-mocks/src/main/java/kong/unirest/core/MockClient.java @@ -0,0 +1,227 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF 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 client for unirest to make requests against + * This implements both sync and async clients + */ +public class MockClient implements Client { + private final Supplier config; + private List routes = new ArrayList<>(); + private SocketSet remoteSocket; + private Invocation defaultResponse; + + public MockClient(Supplier config){ + this.config = config; + } + /** + * Creates a new MockClient and registers it on the primary static UnirestInstance + * @return the Mock Client + */ + public static MockClient register() { + return register(Unirest.primaryInstance()); + } + + /** + * Creates a new MockClient and registers it on the Unirest instance + * @param unirest an instance of Unirest + * @return the Mock Client + */ + public static MockClient register(UnirestInstance unirest) { + MockClient client = new MockClient(unirest::config); + unirest.config().httpClient(client); + return client; + } + + /** + * Clears any MockClient from the primary instance + */ + public static void clear() { + clear(Unirest.primaryInstance()); + } + + /** + * Clears any MockClient from the instance + * @param unirest the instance to clear the mocks from + */ + public static void clear(UnirestInstance unirest) { + if(unirest.config().getClient() instanceof MockClient){ + unirest.config().httpClient((Client) null); + } + } + + @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; + } + + @Override + public CompletableFuture> request(HttpRequest request, + Function> transformer, + CompletableFuture> callback, + Class resultTypes) { + return CompletableFuture.supplyAsync(() -> request(request, transformer, resultTypes)); + } + + @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); + } + + @Override + public CompletableFuture sse(SseRequest request, SseHandler handler) { + return null; + } + + @Override + public Stream sse(SseRequest request) { + return Stream.empty(); + } + + public SocketSet serversSocket() { + if(remoteSocket == null){ + throw new UnirestException("No Socket Yet Established"); + } + return remoteSocket; + } + + + + @Override + public Object getClient() { + return this; + } + + /** + * Start an expectation chain. + * @param method the Http method + * @param path the base path + * @return an Expectation which can have additional criteria added to it. + */ + 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(); + } + + /** + * Expect ANY call to a path with this method + * @param method the Http Method + * @return this expectation builder + */ + public Expectation expect(HttpMethod method) { + return expect(method, null); + } + + /** + * Assert a specific method and path were invoked + * @param method the Http method + * @param path the base path + * @return an Assert object which can have additional criteria chained to it. + */ + 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(); + } + + /** + * Verify that all Expectations were invoked + */ + public void verifyAll() { + routes.forEach(Routes::verifyAll); + } + + /** + * Reset all expectations + */ + public void reset() { + routes.clear(); + defaultResponse = null; + } + + /** + * return this status for any request that doesn't match a expectation + */ + public ExpectedResponse defaultResponse() { + this.defaultResponse = new Invocation(); + return this.defaultResponse.thenReturn(); + } +} diff --git a/unirest/src/main/java/kong/unirest/MappedResponse.java b/unirest-modules-mocks/src/main/java/kong/unirest/core/MockConfig.java similarity index 82% rename from unirest/src/main/java/kong/unirest/MappedResponse.java rename to unirest-modules-mocks/src/main/java/kong/unirest/core/MockConfig.java index 357279733..108f5eea9 100644 --- a/unirest/src/main/java/kong/unirest/MappedResponse.java +++ b/unirest-modules-mocks/src/main/java/kong/unirest/core/MockConfig.java @@ -23,18 +23,16 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; -class MappedResponse extends BaseResponse { - private final V body; - - public MappedResponse(BaseResponse response, V body) { - super(response); - this.body = body; - } +/** + * A Mock Config that will not actually start up any real clients. + */ +public class MockConfig extends Config { + private MockClient client = new MockClient(() -> this); @Override - public V getBody() { - return body; + 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..1bbe5e0d2 --- /dev/null +++ b/unirest-modules-mocks/src/main/java/kong/unirest/core/MockListener.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 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 WebSocket.Lister that records messages and has custom asserts. + */ +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; + + @Override + public void onOpen(WebSocket webSocket) { + open = true; + WebSocket.Listener.super.onOpen(webSocket); + } + + @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); + } + + @Override + public CompletionStage onBinary(WebSocket webSocket, ByteBuffer data, boolean last) { + messagesReceived.add(new Message(data, last)); + return WebSocket.Listener.super.onBinary(webSocket, data, last); + } + + @Override + public CompletionStage onPing(WebSocket webSocket, ByteBuffer message) { + ping = message; + webSocket.sendPong(message); + return WebSocket.Listener.super.onPing(webSocket, message); + } + + @Override + public CompletionStage onPong(WebSocket webSocket, ByteBuffer message) { + this.pong = message; + return WebSocket.Listener.super.onPong(webSocket, message); + } + + @Override + public CompletionStage onClose(WebSocket webSocket, int statusCode, String reason) { + open = false; + closedStatus = statusCode; + closedMessage = reason; + return WebSocket.Listener.super.onClose(webSocket, statusCode, reason); + } + + @Override + public void onError(WebSocket webSocket, Throwable error) { + WebSocket.Listener.super.onError(webSocket, error); + } + + 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); + } + } + + 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); + } + } + + /** + * assert that a ping message was received. + * Note that the onPing method will automatically send a pong to the WebSocket + * @param message the message + */ + public void assertPing(ByteBuffer message) { + if(!Objects.equals(ping, message)){ + throw new UnirestAssertion("Expected Ping Call with buffer %s but got %s", message, ping); + } + } + + /** + * assert that a pong message was received. + * Note that the onPing method will automatically send a pong to the WebSocket + * @param message the message + */ + public void assertPong(ByteBuffer message) { + if(!message.equals(pong)){ + throw new UnirestAssertion("Expected Pong Message %s but got %s", message, pong); + } + } + + public void assertIsOpen() { + if(!open){ + throw new UnirestAssertion("Expected socket to be open but was closed."); + } + } + + private class Message { + private final Object data; + private final boolean last; + + 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..229ac6d98 --- /dev/null +++ b/unirest-modules-mocks/src/main/java/kong/unirest/core/MockRawResponse.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 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; + +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; + + 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; + } + + @Override + public int getStatus() { + return status; + } + + @Override + public String getStatusText() { + return statusMessage; + } + + @Override + public Headers getHeaders() { + return responseHeaders; + } + + @Override + public InputStream getContent() { + return new ByteArrayInputStream(response.getBytes()); + } + + @Override + public byte[] getContentAsBytes() { + return response.getBytes(); + } + + @Override + public String getContentAsString() { + return response; + } + + @Override + public String getContentAsString(String charset) { + if(Objects.isNull(response)){ + return null; + } + return new String(response.getBytes(), tryGetCharset(charset)); + } + + private Charset tryGetCharset(String charset) { + if(Objects.isNull(charset)){ + return StandardCharsets.UTF_8; + } + return Charset.forName(charset); + } + + @Override + public InputStreamReader getContentReader() { + return new InputStreamReader(getContent()); + } + + @Override + public boolean hasContent() { + return response != null; + } + + @Override + public String getContentType() { + return responseHeaders.getFirst("Content-Type"); + } + + @Override + public String getEncoding() { + return responseHeaders.getFirst("Content-Encoding"); + } + + @Override + public Config getConfig() { + return config; + } + + @Override + public HttpResponseSummary toSummary() { + return new ResponseSummary(this); + } + + @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..172546ef7 --- /dev/null +++ b/unirest-modules-mocks/src/main/java/kong/unirest/core/MockWebSocket.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.net.http.WebSocket; +import java.nio.ByteBuffer; +import java.util.concurrent.CompletableFuture; +import java.util.function.BiConsumer; + +/** + * A Mock of a websocket that sends messages directly to a single listener on the other side + */ +public class MockWebSocket implements WebSocket { + private SocketSet remoteSocketSet; + + 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); + } + + @Override + public CompletableFuture sendText(CharSequence data, boolean last) { + return sendToOtherSide((s,l) -> l.onText(s, data, last)); + } + + @Override + public CompletableFuture sendBinary(ByteBuffer data, boolean last) { + return sendToOtherSide((s,l) -> l.onBinary(s, data, last)); + } + + @Override + public CompletableFuture sendPing(ByteBuffer message) { + return sendToOtherSide((s,l) -> l.onPing(s, message)); + } + + @Override + public CompletableFuture sendPong(ByteBuffer message) { + return sendToOtherSide((s,l) -> l.onPong(s, message)); + } + + @Override + public CompletableFuture sendClose(int statusCode, String reason) { + return sendToOtherSide((s,l) -> l.onClose(s, statusCode, reason)); + } + + @Override + public void request(long n) { + + } + + @Override + public String getSubprotocol() { + return null; + } + + @Override + public boolean isOutputClosed() { + return false; + } + + @Override + public boolean isInputClosed() { + return false; + } + + @Override + public void abort() { + + } + + 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..8a8e58deb --- /dev/null +++ b/unirest-modules-mocks/src/main/java/kong/unirest/core/Routes.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.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) { + Map map = new TreeMap<>(); + invokes.stream() + .forEach(i -> { + Integer score = i.scoreMatch(request); + if(score >= 0) { + map.put(score, i); + } + }); + if (map.size() == 0) { + return Optional.empty(); + } + Invocation value = map.get(Collections.max(map.keySet())); + value.log(request); + return Optional.of(value); + } + + @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..c817bb158 --- /dev/null +++ b/unirest-modules-mocks/src/main/java/kong/unirest/core/SocketSet.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.net.http.WebSocket; + +/** + * A socket set represents a websocket and the listener for a target. + * Each side of a websocket communication would be represented by a set + */ +public class SocketSet { + + private final S socket; + private final L listener; + private final String name; + + public SocketSet(S socket, L listener, String name){ + this.socket = socket; + this.listener = listener; + this.name = name; + } + + public S getSocket() { + return socket; + } + + public L getListener() { + return listener; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return name; + } + + 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..f30edb1b4 --- /dev/null +++ b/unirest-modules-mocks/src/main/java/kong/unirest/core/Times.java @@ -0,0 +1,135 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF 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 abstract class Times { + + public static Times exactlyOnce() { + return exactly(1); + } + + public static Times exactly(int times) { + return new Exactly(times); + } + + public static Times atLeastOnce() { + return atLeast(1); + } + + public static Times atLeast(int times) { + return new AtLeast(times); + } + + public static Times never() { + return exactly(0); + } + + public static Times atMost(int times) { + return new AtMost(times); + } + + public abstract EvaluationResult matches(int number); + + 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); + } + } + } + + 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); + } + } + } + + 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); + } + } + } + + public static class EvaluationResult { + private static final EvaluationResult SUCCESS = new EvaluationResult(true, null); + private final boolean success; + private final String message; + + public static EvaluationResult success(){ + return SUCCESS; + } + + public static EvaluationResult fail(String message, Object... values){ + return new EvaluationResult(false, String.format(message, values)); + } + + public EvaluationResult(boolean success, String message){ + this.success = success; + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + 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..d7a575a21 --- /dev/null +++ b/unirest-modules-mocks/src/main/java/kong/unirest/core/UnirestAssertion.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; + +/** + * Thrown to indicate that an assertion has failed. + */ +public class UnirestAssertion extends AssertionError { + 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/src/test/java/kong/unirest/apache/SecurityConfigTest.java b/unirest-modules-mocks/src/test/java/kong/tests/MetricsTest.java similarity index 67% rename from unirest/src/test/java/kong/unirest/apache/SecurityConfigTest.java rename to unirest-modules-mocks/src/test/java/kong/tests/MetricsTest.java index 1bbcd2083..3fb67bfc8 100644 --- a/unirest/src/test/java/kong/unirest/apache/SecurityConfigTest.java +++ b/unirest-modules-mocks/src/test/java/kong/tests/MetricsTest.java @@ -23,25 +23,32 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest.apache; +package kong.tests; -import kong.unirest.Config; -import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; -import org.junit.Test; +import kong.unirest.core.HttpMethod; +import kong.unirest.core.Unirest; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.assertTrue; -public class SecurityConfigTest { +class MetricsTest extends Base { + + boolean wasCalled = false; @Test - public void willTakeConfigIntoAccountWhenCreatingManager() { - Config config = new Config().concurrency(88, 42); + void willExecuteMetrics() { + Unirest.config().instrumentWith( + r -> (re, ex) -> { + wasCalled = true; + } + ); - SecurityConfig sec = new SecurityConfig(config); + client.expect(HttpMethod.GET, "http://foo").thenReturn(); - PoolingHttpClientConnectionManager manager = sec.createManager(); + Unirest.get("http://foo").asEmpty(); - assertEquals(88, manager.getMaxTotal()); - assertEquals(42, manager.getDefaultMaxPerRoute()); + assertTrue(wasCalled); } -} \ No newline at end of file + + +} 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..e28b61994 --- /dev/null +++ b/unirest-modules-mocks/src/test/java/kong/tests/MultipleExpectsTest.java @@ -0,0 +1,119 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF 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 whenDuplicateExpectsExistUseTheLastOne() { + client.expect(GET, path) + .queryString("monster", "grover") + .thenReturn("one"); + + client.expect(GET, path) + .queryString("monster", "grover") + .thenReturn("two"); + + String result = Unirest.get(path) + .queryString("monster", "grover") + .asString() + .getBody(); + + assertEquals("two", result); + + assertException(() -> client.verifyAll(), + "Expected at least 1 invocations but got 0\n" + + "GET http://basic\n" + + "Params:\n" + + "monster: grover"); + } +} diff --git a/unirest/src/main/java/kong/unirest/EmptyResponse.java b/unirest-modules-mocks/src/test/java/kong/tests/Pojo.java similarity index 85% rename from unirest/src/main/java/kong/unirest/EmptyResponse.java rename to unirest-modules-mocks/src/test/java/kong/tests/Pojo.java index f7dd4f27e..c384922bb 100644 --- a/unirest/src/main/java/kong/unirest/EmptyResponse.java +++ b/unirest-modules-mocks/src/test/java/kong/tests/Pojo.java @@ -23,15 +23,14 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.tests; -class EmptyResponse extends BaseResponse { - EmptyResponse(RawResponse response) { - super(response); - } +public class Pojo { + public String fruit; + + public Pojo(){} - @Override - public Empty getBody() { - return null; + 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..3093e9f4d --- /dev/null +++ b/unirest-modules-mocks/src/test/java/kong/tests/VerifyTimesTest.java @@ -0,0 +1,150 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF 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); + + assertThrows(UnirestAssertion.class, response::verify); + + 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 index 3975046e4..5e36a57aa 100644 --- a/unirest/pom.xml +++ b/unirest/pom.xml @@ -4,126 +4,15 @@ com.konghq unirest-java-parent - 2.3.08-SNAPSHOT + 4.8.2-SNAPSHOT Simplified, lightweight HTTP client library. - unirest-java + unirest-java-core + unirest-java-core jar ${project.parent.basedir} - - - - org.apache.httpcomponents - httpclient - 4.5.9 - - - org.apache.httpcomponents - httpmime - 4.5.9 - - - org.apache.httpcomponents - httpasyncclient - 4.1.4 - - - org.json - json - 20180813 - - - junit - junit - 4.12 - test - - - - - - org.hamcrest - hamcrest-all - 1.3 - test - - - commons-io - commons-io - 2.6 - test - - - - org.mockito - mockito-all - 2.0.2-beta - test - - - - com.google.guava - guava - 28.0-jre - test - - - - com.fasterxml.jackson.datatype - jackson-datatype-guava - ${jackson.version} - test - - - com.fasterxml.jackson.core - jackson-databind - ${jackson.version} - test - - - com.sparkjava - spark-core - 2.9.1 - test - - - org.skyscreamer - jsonassert - 1.5.0 - test - - - org.slf4j - slf4j-simple - 1.7.26 - test - - - com.google.code.gson - gson - 2.8.5 - test - - - com.github.paweladamski - HttpClientMock - 1.5.0 - test - - - - - - - - org.apache.maven.plugins - maven-shade-plugin - - - - diff --git a/unirest/src/main/java/kong/unirest/BaseRequest.java b/unirest/src/main/java/kong/unirest/BaseRequest.java deleted file mode 100644 index 4f5216818..000000000 --- a/unirest/src/main/java/kong/unirest/BaseRequest.java +++ /dev/null @@ -1,354 +0,0 @@ -/** - * The MIT License - * - * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF 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; - -import java.io.File; -import java.util.*; -import java.util.concurrent.CompletableFuture; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; - -abstract class BaseRequest implements HttpRequest { - - 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 socketTimeout; - private Integer connectTimeout; - private Proxy proxy; - - BaseRequest(BaseRequest httpRequest) { - this.config = httpRequest.config; - this.method = httpRequest.method; - this.url = httpRequest.url; - this.headers.putAll(httpRequest.headers); - this.socketTimeout = httpRequest.socketTimeout; - this.connectTimeout = httpRequest.connectTimeout; - this.proxy = httpRequest.proxy; - } - - BaseRequest(Config config, HttpMethod method, String url) { - this.config = config; - this.method = method; - this.url = new Path(url); - 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.replace("Authorization", Util.toBasicAuthValue(username, password)); - return (R)this; - } - - @Override - public R accept(String value) { - return header(HeaderNames.ACCEPT, value); - } - - @Override - public R responseEncoding(String encoding) { - this.responseEncoding = encoding; - 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) { - if (headerMap != null) { - for (Map.Entry entry : headerMap.entrySet()) { - header(entry.getKey(), entry.getValue()); - } - } - 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 socketTimeout(int millies) { - this.socketTimeout = millies; - return (R)this; - } - - @Override - public R connectTimeout(int millies) { - this.connectTimeout = millies; - return (R)this; - } - - @Override - public R proxy(String host, int port) { - this.proxy = new Proxy(host, port); - 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 HttpResponse asEmpty() { - return config.getClient().request(this, EmptyResponse::new); - } - - @Override - public CompletableFuture> asEmptyAsync() { - return config.getAsyncClient() - .request(this, EmptyResponse::new, new CompletableFuture<>()); - } - - @Override - public CompletableFuture> asEmptyAsync(Callback callback) { - return config.getAsyncClient() - .request(this, EmptyResponse::new, CallbackFuture.wrap(callback)); - } - - @Override - public HttpResponse asString() throws UnirestException { - return config.getClient().request(this, r -> new StringResponse(r, responseEncoding)); - } - - @Override - public CompletableFuture> asStringAsync() { - return config.getAsyncClient() - .request(this, r -> new StringResponse(r, responseEncoding), new CompletableFuture<>()); - } - - @Override - public CompletableFuture> asStringAsync(Callback callback) { - return config.getAsyncClient() - .request(this, r -> new StringResponse(r, responseEncoding), CallbackFuture.wrap(callback)); - } - - @Override - public HttpResponse asJson() throws UnirestException { - return config.getClient().request(this, JsonResponse::new); - } - - @Override - public CompletableFuture> asJsonAsync() { - - return config.getAsyncClient().request(this, JsonResponse::new, new CompletableFuture<>()); - } - - @Override - public CompletableFuture> asJsonAsync(Callback callback) { - - return config.getAsyncClient().request(this, JsonResponse::new, CallbackFuture.wrap(callback)); - } - - @Override - public HttpResponse asObject(Class responseClass) throws UnirestException { - return config.getClient().request(this, r -> new ObjectResponse(getObjectMapper(), r, responseClass)); - } - - @Override - public HttpResponse asObject(GenericType genericType) throws UnirestException { - return config.getClient().request(this, r -> new ObjectResponse(getObjectMapper(), r, genericType)); - } - - @Override - public HttpResponse asObject(Function function) { - return config.getClient().request(this, funcResponse(function)); - } - - @Override - public CompletableFuture> asObjectAsync(Function function) { - - return config.getAsyncClient().request(this, funcResponse(function), new CompletableFuture<>()); - } - - @Override - public CompletableFuture> asObjectAsync(Class responseClass) { - - return config.getAsyncClient().request(this, r -> new ObjectResponse(getObjectMapper(), r, responseClass), new CompletableFuture<>()); - } - - @Override - public CompletableFuture> asObjectAsync(Class responseClass, Callback callback) { - - return config.getAsyncClient().request(this, r -> new ObjectResponse<>(getObjectMapper(), r, responseClass), CallbackFuture.wrap(callback)); - } - - @Override - public CompletableFuture> asObjectAsync(GenericType genericType) { - - return config.getAsyncClient().request(this, r -> new ObjectResponse<>(getObjectMapper(), r, genericType), new CompletableFuture<>()); - } - - @Override - public CompletableFuture> asObjectAsync(GenericType genericType, Callback callback) { - - return config.getAsyncClient().request(this, r -> new ObjectResponse<>(getObjectMapper(), r, genericType), CallbackFuture.wrap(callback)); - } - - private Function> funcResponse(Function function) { - return r -> new BasicResponse<>(r, function.apply(r)); - } - - @Override - public void thenConsume(Consumer consumer) { - config.getClient().request(this, getConsumer(consumer)); - } - - @Override - public void thenConsumeAsync(Consumer consumer) { - config.getAsyncClient().request(this, getConsumer(consumer), new CompletableFuture<>()); - } - - @Override - public HttpResponse asFile(String path) { - return config.getClient().request(this, r -> new FileResponse(r, path)); - } - - @Override - public CompletableFuture> asFileAsync(String path) { - return config.getAsyncClient().request(this, r -> new FileResponse(r, path), new CompletableFuture<>()); - } - - @Override - public CompletableFuture> asFileAsync(String path, Callback callback) { - return config.getAsyncClient().request(this, r -> new FileResponse(r, path), CallbackFuture.wrap(callback)); - } - - @Override - public PagedList asPaged(Function mappingFunction, Function, String> linkExtractor) { - PagedList all = new PagedList<>(); - String nextLink = this.getUrl(); - do { - this.url = new Path(nextLink); - HttpResponse next = mappingFunction.apply(this); - all.add(next); - nextLink = linkExtractor.apply(next); - }while (!Util.isNullOrEmpty(nextLink)); - return all; - } - - - - private Function> getConsumer(Consumer consumer) { - return r -> { - consumer.accept(r); - return null; - }; - } - - @Override - public HttpMethod getHttpMethod() { - return method; - } - - @Override - public String getUrl() { - return url.toString(); - } - - @Override - public Headers getHeaders() { - return headers; - } - - private ObjectMapper getObjectMapper() { - return objectMapper.orElseGet(config::getObjectMapper); - } - - @Override - public int getSocketTimeout() { - return valueOr(socketTimeout, config::getSocketTimeout); - } - - @Override - public int getConnectTimeout() { - return valueOr(connectTimeout, config::getConnectionTimeout); - } - - @Override - public Proxy getProxy() { - return valueOr(proxy, config::getProxy); - } - - @Override - public HttpRequestSummary toSummary() { - return new RequestSummary(this); - } - - private T valueOr(T x, Supplier o){ - if(x != null){ - return x; - } - return o.get(); - } - - Path getPath() { - return url; - } -} diff --git a/unirest/src/main/java/kong/unirest/Config.java b/unirest/src/main/java/kong/unirest/Config.java deleted file mode 100644 index 824733399..000000000 --- a/unirest/src/main/java/kong/unirest/Config.java +++ /dev/null @@ -1,712 +0,0 @@ -/** - * The MIT License - * - * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF 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; - -import kong.unirest.apache.ApacheAsyncClient; -import kong.unirest.apache.ApacheClient; -import org.apache.http.HttpRequestInterceptor; -import org.apache.http.client.HttpClient; -import org.apache.http.nio.client.HttpAsyncClient; - -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.security.KeyStore; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.stream.Collectors; -import java.util.stream.Stream; - - -public class Config { - public static final int DEFAULT_CONNECTION_TIMEOUT = 10000; - public static final int DEFAULT_MAX_CONNECTIONS = 200; - public static final int DEFAULT_MAX_PER_ROUTE = 20; - public static final int DEFAULT_CONNECT_TIMEOUT = 10000; - public static final int DEFAULT_SOCKET_TIMEOUT = 60000; - - private Optional client = Optional.empty(); - private Optional asyncClient = Optional.empty(); - private Optional objectMapper = Optional.empty(); - - private List interceptors = new ArrayList<>(); - private Headers headers; - private Proxy proxy; - private int connectionTimeout; - private int socketTimeout; - private int maxTotal; - private int maxPerRoute; - private boolean followRedirects; - private boolean cookieManagement; - private boolean useSystemProperties; - private String defaultResponseEncoding = StandardCharsets.UTF_8.name(); - private Function asyncBuilder; - private Function clientBuilder; - private boolean requestCompressionOn = true; - private boolean automaticRetries; - private boolean verifySsl = true; - private boolean addShutdownHook = false; - private KeyStore keystore; - private Supplier keystorePassword = () -> null; - private String cookieSpec; - private UniMetric metrics = new NoopMetric(); - - public Config() { - setDefaults(); - try { - asyncBuilder = ApacheAsyncClient::new; - clientBuilder = ApacheClient::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); - } - } - - private void setDefaults() { - interceptors.clear(); - proxy = null; - headers = new Headers(); - connectionTimeout = DEFAULT_CONNECTION_TIMEOUT; - socketTimeout = DEFAULT_SOCKET_TIMEOUT; - maxTotal = DEFAULT_MAX_CONNECTIONS; - maxPerRoute = DEFAULT_MAX_PER_ROUTE; - followRedirects = true; - cookieManagement = true; - requestCompressionOn = true; - automaticRetries = true; - verifySsl = true; - keystore = null; - keystorePassword = null; - } - - /** - * Set the HttpClient implementation to use for every synchronous request - * - * @param httpClient Custom httpClient implementation - * @return this config object - */ - public Config httpClient(HttpClient httpClient) { - client = Optional.of(new ApacheClient(httpClient, this, null, null)); - return this; - } - - /** - * 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.of(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; - } - - /** - * Set the asynchronous AbstractHttpAsyncClient implementation to use for every asynchronous request - * - * @param value Custom CloseableHttpAsyncClient implementation - * @return this config object - */ - public Config asyncClient(HttpAsyncClient value) { - this.asyncClient = Optional.of(new ApacheAsyncClient(value, this, null, null)); - return this; - } - - /** - * Set the full async configuration including monitors. These will be shutDown on a Unirest.shudown() - * - * @param value Custom AsyncConfig class. The actual AsyncHttpClient is required. - * @return this config object - */ - public Config asyncClient(AsyncClient value) { - asyncClient = Optional.of(value); - return this; - } - - /** - * Set the full async configuration including monitors. These will be shutDown on a Unirest.shudown() - * - * @param asyncClientBuilder A builder function for creating a AsyncClient - * @return this config object - */ - public Config asyncClient(Function asyncClientBuilder) { - this.asyncBuilder = asyncClientBuilder; - return this; - } - - /** - * Set a proxy - * - * @param value Proxy settings object. - * @return this config object - */ - public Config proxy(Proxy value) { - validateClientsNotRunning(); - this.proxy = 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 = Optional.ofNullable(om); - return this; - } - - /** - * 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 - */ - public Config clientCertificateStore(KeyStore store, String password) { - 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 - */ - public Config clientCertificateStore(String fileLocation, String password) { - 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; - } - - /** - * Set the connection timeout - * - * @param inMillies The timeout until a connection with the server is established (in milliseconds). Default is 10000. Set to zero to disable the timeout. - * @return this config object - */ - public Config connectTimeout(int inMillies) { - validateClientsNotRunning(); - this.connectionTimeout = inMillies; - return this; - } - - /** - * Set the socket timeout - * - * @param inMillies The timeout to receive data (in milliseconds). Default is 60000. Set to zero to disable the timeout. - * @return this config object - */ - public Config socketTimeout(int inMillies) { - validateClientsNotRunning(); - this.socketTimeout = inMillies; - return this; - } - - /** - * Set the concurrency levels - * - * @param total Defines the overall connection limit for a connection pool. Default is 200. - * @param perRoute Defines a connection limit per one HTTP route (this can be considered a per target host limit). Default is 20. - * @return this config object - */ - public Config concurrency(int total, int perRoute) { - validateClientsNotRunning(); - this.maxTotal = total; - this.maxPerRoute = perRoute; - return this; - } - - /** - * Clear default headers - * @return this config object - */ - public Config clearDefaultHeaders() { - headers.clear(); - return this; - } - - /** - * Default basic auth credentials - * @param username the username - * @param password the password - * @return this config object - */ - 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; - } - - /** - * 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 HttpRequestInterceptor to the clients. This can be called multiple times to add as many as you like. - * https://hc.apache.org/httpcomponents-core-ga/httpcore/apidocs/org/apache/http/HttpRequestInterceptor.html - * - * @param interceptor The addInterceptor - * @return this config object - */ - public Config addInterceptor(HttpRequestInterceptor interceptor) { - validateClientsNotRunning(); - interceptors.add(interceptor); - return this; - } - - /** - * Allow the client to follow redirects. Defaults to TRUE - * - * @param enable The name of the header. - * @return this config object - */ - public Config followRedirects(boolean enable) { - validateClientsNotRunning(); - this.followRedirects = enable; - return this; - } - - /** - * Allow the client to manage cookies. Defaults to TRUE - * - * @param enable The name of the header. - * @return this config object - */ - public Config enableCookieManagement(boolean enable) { - validateClientsNotRunning(); - this.cookieManagement = enable; - return this; - } - - /** - * Toggle verifying SSL/TLS certificates. Defaults to TRUE - * - * @param value a bool is its true or not. - * @return this config object - */ - public Config verifySsl(boolean value) { - this.verifySsl = value; - return this; - } - - /** - * Tell the HttpClients to use the system properties for things like proxies - * - * @param value a bool is its true or not. - * @return this config object - */ - public Config useSystemProperties(boolean value) { - this.useSystemProperties = value; - return this; - } - - /** - * Turn on or off requesting all content as compressed. (GZIP encoded) - * Default is true - * - * @param value a bool is its true or not. - * @return this config object - */ - public Config requestCompression(boolean value) { - this.requestCompressionOn = value; - return this; - } - - /** - * Automaticly retry certain recoverable errors like socket timeouts. Up to 4 times - * Note that currently this only works on synchronous calls. - * Default is true - * - * @param value a bool is its true or not. - * @return this config object - */ - public Config automaticRetries(boolean value) { - automaticRetries = value; - return this; - } - - /** - * Sets a cookie policy - * Acceptable values: - * 'default' (same as Netscape), - * 'netscape', - * 'ignoreCookies', - * 'standard' (RFC 6265 interoprability profile) , - * 'standard-strict' (RFC 6265 strict profile) - * - * @param policy: the policy for cookies to follow - * @return this config object - */ - public Config cookieSpec(String policy) { - this.cookieSpec = policy; - return this; - } - - /** - * Set the default encoding that will be used for serialization into Strings. - * The default-default is UTF-8 - * - * @param value a bool is its true or not. - * @return this config object - */ - public Config setDefaultResponseEncoding(String value) { - Objects.requireNonNull(value, "Encoding cannot be null"); - this.defaultResponseEncoding = value; - return this; - } - - /** - * Register the client with a system shutdown hook. Note that this creates up to two threads - * (depending on if you use both sync and async clients). default is false - * - * @param value a bool is its true or not. - * @return this config object - */ - public Config addShutdownHook(boolean value) { - this.addShutdownHook = value; - if (value) { - client.ifPresent(Client::registerShutdownHook); - asyncClient.ifPresent(AsyncClient::registerShutdownHook); - } - return this; - } - - /** - * Return default headers that are added to every request - * - * @return Headers - */ - public Headers getDefaultHeaders() { - return headers; - } - - /** - * Does the config have currently running clients? Find out here. - * - * @return boolean - */ - public boolean isRunning() { - return client.isPresent() || asyncClient.isPresent(); - } - - /** - * Shutdown the current config and re-init. - * - * @return this config - */ - public Config reset() { - shutDown(false); - return this; - } - - - /** - * Shut down the configuration and its clients. - * The config can be re-initialized with its settings - * - * @param clearOptions should the current non-client settings be retained. - */ - public void shutDown(boolean clearOptions) { - List ex = Stream.concat( - client.map(Client::close).orElseGet(Stream::empty), - asyncClient.map(AsyncClient::close).orElseGet(Stream::empty) - ).collect(Collectors.toList()); - - client = Optional.empty(); - asyncClient = Optional.empty(); - - if (clearOptions) { - setDefaults(); - } - - if (!ex.isEmpty()) { - throw new UnirestException(ex); - } - } - - /** - * Return the current Client. One will be build if it does - * not yet exist. - * - * @return A synchronous Client - */ - public Client getClient() { - if (!client.isPresent()) { - buildClient(); - } - return client.get(); - } - - private synchronized void buildClient() { - if (!client.isPresent()) { - client = Optional.of(clientBuilder.apply(this)); - } - } - - /** - * Return the current HttpAsyncClient. One will be build if it does - * not yet exist. - * - * @return Apache HttpAsyncClient - */ - public AsyncClient getAsyncClient() { - if (!asyncClientIsReady()) { - buildAsyncClient(); - } - return asyncClient.get(); - } - - private boolean asyncClientIsReady() { - return asyncClient - .map(AsyncClient::isRunning) - .orElse(false); - } - - private synchronized void buildAsyncClient() { - if (!asyncClientIsReady()) { - AsyncClient value = asyncBuilder.apply(this); - verifyIsOn(value); - asyncClient = Optional.of(value); - } - } - - private void verifyIsOn(AsyncClient value) { - if (!value.isRunning()) { - throw new UnirestConfigException("Attempted to get a new async client but it was not started. Please ensure it is"); - } - } - - // Accessors for unirest. - - /** - * @return if cookie management should be enabled. - * default: true - */ - public boolean getEnabledCookieManagement() { - return cookieManagement; - } - - /** - * @return if the clients should follow redirects - * default: true - */ - public boolean getFollowRedirects() { - return followRedirects; - } - - /** - * @return the maximum number of connections the clients for this config will manage at once - * default: 200 - */ - public int getMaxConnections() { - return maxTotal; - } - - /** - * @return the maximum number of connections per route the clients for this config will manage at once - * default: 20 - */ - public int getMaxPerRoutes() { - return maxPerRoute; - } - - /** - * @return the connection timeout in milliseconds - * default: 10000 - */ - public int getConnectionTimeout() { - return connectionTimeout; - } - - /** - * @return socket timeout in milliseconds - * default: 60000 - */ - public int getSocketTimeout() { - return socketTimeout; - } - - /** - * @return a security keystore if one has been provided - */ - public KeyStore getKeystore() { - return this.keystore; - } - - /** - * @return The password for the keystore if provided - */ - public String getKeyStorePassword() { - return this.keystorePassword.get(); - } - - /** - * @return a configured object mapper - * @throws UnirestException if none has been configured. - */ - public ObjectMapper getObjectMapper() { - return objectMapper.orElseThrow(() -> new UnirestException("No Object Mapper Configured. Please config one with Unirest.config().setObjectMapper")); - } - - private void validateClientsNotRunning() { - if (client.isPresent() || asyncClient.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." - ); - } - } - - public List getInterceptors() { - return interceptors; - } - - public Proxy getProxy() { - return proxy; - } - - public boolean useSystemProperties() { - return this.useSystemProperties; - } - - public String getDefaultResponseEncoding() { - return defaultResponseEncoding; - } - - public boolean isRequestCompressionOn() { - return requestCompressionOn; - } - - public boolean isAutomaticRetries() { - return automaticRetries; - } - - public boolean isVerifySsl() { - return verifySsl; - } - - public boolean shouldAddShutdownHook() { - return addShutdownHook; - } - - public String getCookieSpec() { - return cookieSpec; - } - - public UniMetric getMetric() { - return metrics; - } -} diff --git a/unirest/src/main/java/kong/unirest/HttpRequestWithBody.java b/unirest/src/main/java/kong/unirest/HttpRequestWithBody.java deleted file mode 100644 index fb0103ef3..000000000 --- a/unirest/src/main/java/kong/unirest/HttpRequestWithBody.java +++ /dev/null @@ -1,75 +0,0 @@ -/** - * The MIT License - * - * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF 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; - -import org.json.JSONArray; -import org.json.JSONObject; - -import java.io.File; -import java.io.InputStream; -import java.nio.charset.Charset; -import java.util.Collection; -import java.util.Map; - -public interface HttpRequestWithBody extends HttpRequest { - HttpRequestWithBody charset(Charset charset); - - /** - * Forces the request to send as multipart even if all params are simple - * @return The same MultipartBody - */ - MultipartBody multiPartContent(); - - MultipartBody field(String name, Collection value); - - MultipartBody field(String name, File file); - - MultipartBody field(String name, File file, String contentType); - - MultipartBody field(String name, Object value); - - MultipartBody field(String name, Object value, String contentType); - - MultipartBody fields(Map parameters); - - MultipartBody field(String name, InputStream stream, ContentType contentType, String fileName); - - MultipartBody field(String name, InputStream stream, String fileName); - - RequestBodyEntity body(JsonNode body); - - RequestBodyEntity body(String body); - - RequestBodyEntity body(Object body); - - RequestBodyEntity body(byte[] body); - - RequestBodyEntity body(JSONObject body); - - RequestBodyEntity body(JSONArray body); - - Charset getCharset(); -} diff --git a/unirest/src/main/java/kong/unirest/apache/ApacheAsyncClient.java b/unirest/src/main/java/kong/unirest/apache/ApacheAsyncClient.java deleted file mode 100644 index a22629441..000000000 --- a/unirest/src/main/java/kong/unirest/apache/ApacheAsyncClient.java +++ /dev/null @@ -1,203 +0,0 @@ -/** - * The MIT License - * - * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF 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.apache; - -import kong.unirest.*; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.concurrent.FutureCallback; -import org.apache.http.config.Registry; -import org.apache.http.config.RegistryBuilder; -import org.apache.http.conn.ssl.NoopHostnameVerifier; -import org.apache.http.conn.ssl.TrustStrategy; -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.client.HttpAsyncClient; -import org.apache.http.nio.conn.NoopIOSessionStrategy; -import org.apache.http.nio.conn.SchemeIOSessionStrategy; -import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy; -import org.apache.http.ssl.SSLContextBuilder; - -import java.util.Objects; -import java.util.concurrent.CompletableFuture; -import java.util.function.Function; -import java.util.stream.Stream; - - -public class ApacheAsyncClient extends BaseApacheClient implements AsyncClient { - - private final HttpAsyncClient client; - private final AsyncIdleConnectionMonitorThread syncMonitor; - private final PoolingNHttpClientConnectionManager manager; - private Config config; - private boolean hookset; - - public ApacheAsyncClient(Config config) { - this.config = config; - try { - manager = createConnectionManager(); - manager.setMaxTotal(config.getMaxConnections()); - manager.setDefaultMaxPerRoute(config.getMaxPerRoutes()); - - HttpAsyncClientBuilder ab = HttpAsyncClientBuilder.create() - .setDefaultRequestConfig(RequestOptions.toRequestConfig(config)) - .setConnectionManager(manager) - .setDefaultCredentialsProvider(toApacheCreds(config.getProxy())) - .useSystemProperties(); - - setOptions(ab); - - CloseableHttpAsyncClient build = ab.build(); - build.start(); - syncMonitor = new AsyncIdleConnectionMonitorThread(manager); - syncMonitor.tryStart(); - client = build; - if(config.shouldAddShutdownHook()){ - registerShutdownHook(); - } - } catch (Exception e) { - throw new UnirestConfigException(e); - } - } - - @Override - public void registerShutdownHook() { - if(!hookset) { - hookset = true; - Runtime.getRuntime().addShutdownHook(new Thread(this::close, "Unirest Apache Async Client Shutdown Hook")); - } - } - - private void setOptions(HttpAsyncClientBuilder ab) { - if(!config.isVerifySsl()){ - disableSsl(ab); - } - if(config.useSystemProperties()){ - ab.useSystemProperties(); - } - if (!config.getFollowRedirects()) { - ab.setRedirectStrategy(new ApacheNoRedirectStrategy()); - } - if (!config.getEnabledCookieManagement()) { - ab.disableCookieManagement(); - } - config.getInterceptors().forEach(ab::addInterceptorFirst); - } - - private PoolingNHttpClientConnectionManager createConnectionManager() throws Exception { - if(config.isVerifySsl()) { - return new PoolingNHttpClientConnectionManager(new DefaultConnectingIOReactor()); - } - Registry registry = RegistryBuilder.create() - .register("http", NoopIOSessionStrategy.INSTANCE) - .register("https", new SSLIOSessionStrategy(new SSLContextBuilder() - .loadTrustMaterial(null, (x509Certificates, s) -> true) - .build(), NoopHostnameVerifier.INSTANCE)) - .build(); - - return new PoolingNHttpClientConnectionManager(new DefaultConnectingIOReactor(), registry); - } - - private void disableSsl(HttpAsyncClientBuilder ab) { - try { - ab.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE); - ab.setSSLContext(new SSLContextBuilder().loadTrustMaterial(null, (TrustStrategy) (arg0, arg1) -> true).build()); - }catch (Exception e){ - throw new UnirestConfigException(e); - } - } - - public ApacheAsyncClient(HttpAsyncClient client, - Config config, - PoolingNHttpClientConnectionManager manager, - AsyncIdleConnectionMonitorThread monitor) { - Objects.requireNonNull(client, "Client may not be null"); - this.config = config; - this.client = client; - this.syncMonitor = monitor; - this.manager = manager; - } - - @Override - public CompletableFuture> request( - HttpRequest request, - Function> transformer, - CompletableFuture> callback) { - - Objects.requireNonNull(callback); - - HttpUriRequest requestObj = new RequestPrep(request, config, true).prepare(); - MetricContext metric = config.getMetric().begin(request.toSummary()); - client.execute(requestObj, new FutureCallback() { - @Override - public void completed(org.apache.http.HttpResponse httpResponse) { - ApacheResponse t = new ApacheResponse(httpResponse, config); - metric.complete(t.toSummary(), null); - callback.complete(transformBody(transformer, t)); - } - - @Override - public void failed(Exception e) { - metric.complete(null, e); - callback.completeExceptionally(e); - } - - @Override - public void cancelled() { - UnirestException canceled = new UnirestException("canceled"); - metric.complete(null, canceled); - callback.completeExceptionally(canceled); - } - }); - return callback; - } - - @Override - public boolean isRunning() { - return Util.tryCast(client, CloseableHttpAsyncClient.class) - .map(CloseableHttpAsyncClient::isRunning) - .orElse(true); - } - - @Override - public HttpAsyncClient getClient() { - return client; - } - - @Override - public Stream close() { - return Util.collectExceptions(Util.tryCast(client, CloseableHttpAsyncClient.class) - .filter(c -> c.isRunning()) - .map(c -> Util.tryDo(c, d -> d.close())) - .filter(c -> c.isPresent()) - .map(c -> c.get()), - Util.tryDo(manager, m -> m.shutdown()), - Util.tryDo(syncMonitor, m -> m.interrupt())); - } - - -} diff --git a/unirest/src/main/java/kong/unirest/apache/ApacheBodyMapper.java b/unirest/src/main/java/kong/unirest/apache/ApacheBodyMapper.java deleted file mode 100644 index 6062e1c47..000000000 --- a/unirest/src/main/java/kong/unirest/apache/ApacheBodyMapper.java +++ /dev/null @@ -1,149 +0,0 @@ -/** - * The MIT License - * - * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF 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.apache; - -import kong.unirest.*; -import org.apache.http.HttpEntity; -import org.apache.http.NameValuePair; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.entity.BasicHttpEntity; -import org.apache.http.entity.ByteArrayEntity; -import org.apache.http.entity.StringEntity; -import org.apache.http.entity.mime.HttpMultipartMode; -import org.apache.http.entity.mime.MultipartEntityBuilder; -import org.apache.http.entity.mime.content.*; -import org.apache.http.message.BasicNameValuePair; - -import java.io.File; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Optional; - -class ApacheBodyMapper { - - private final HttpRequest request; - - ApacheBodyMapper(HttpRequest request){ - this.request = request; - } - - HttpEntity apply() { - Optional body = request.getBody(); - return body.map(this::applyBody).orElseGet(BasicHttpEntity::new); - - } - - private HttpEntity applyBody(Body o) { - if(o.isEntityBody()){ - return mapToUniBody(o); - }else { - return mapToMultipart(o); - } - } - - private HttpEntity mapToUniBody(Body b) { - BodyPart bodyPart = b.uniPart(); - if(bodyPart == null){ - return new StringEntity("", StandardCharsets.UTF_8); - } else if(String.class.isAssignableFrom(bodyPart.getPartType())){ - return new StringEntity((String) bodyPart.getValue(), b.getCharset()); - } else { - return new ByteArrayEntity((byte[])bodyPart.getValue()); - } - } - - private HttpEntity mapToMultipart(Body body) { - if (body.isMultiPart()) { - MultipartEntityBuilder builder = MultipartEntityBuilder.create(); - builder.setCharset(body.getCharset()); - builder.setMode(HttpMultipartMode.valueOf(body.getMode().name())); - for (BodyPart key : body.multiParts()) { - builder.addPart(key.getName(), apply(key, body)); - } - return builder.build(); - } else { - return new UrlEncodedFormEntity(getList(body.multiParts()), body.getCharset()); - } - } - - private ContentBody apply(BodyPart value, Body body) { - if (is(value, File.class)) { - return toFileBody(value, body); - } else if (is(value, InputStream.class)) { - return toInputStreamBody(value, body); - } else if (is(value, byte[].class)) { - return toByteArrayBody(value); - } else { - return toStringBody(value); - } - } - - private ContentBody toFileBody(BodyPart value, Body body) { - File file = (File)value.getValue(); - return new MonitoringFileBody(value.getName(), file, toApacheType(value.getContentType()), body.getMonitor()); - } - - private ContentBody toInputStreamBody(BodyPart value, Body body) { - InputStream part = (InputStream)value.getValue(); - return new MonitoringStreamBody(part, - toApacheType(value.getContentType()), - value.getFileName(), - value.getName(), - body.getMonitor()); - } - - - - private ContentBody toByteArrayBody(BodyPart value) { - byte[] part = (byte[])value.getValue(); - return new ByteArrayBody(part, - toApacheType(value.getContentType()), - value.getFileName()); - } - - private ContentBody toStringBody(BodyPart value) { - return new StringBody(String.valueOf(value.getValue()), toApacheType(value.getContentType())); - } - - private boolean is(BodyPart value, Class cls) { - return cls.isAssignableFrom(value.getPartType()); - } - - private org.apache.http.entity.ContentType toApacheType(String type) { - return org.apache.http.entity.ContentType.parse(type); - } - - static List getList(Collection parameters) { - List result = new ArrayList<>(); - for (BodyPart entry : parameters) { - result.add(new BasicNameValuePair(entry.getName(), entry.getValue().toString())); - } - return result; - } -} diff --git a/unirest/src/main/java/kong/unirest/apache/ApacheClient.java b/unirest/src/main/java/kong/unirest/apache/ApacheClient.java deleted file mode 100644 index 0e0b62d5a..000000000 --- a/unirest/src/main/java/kong/unirest/apache/ApacheClient.java +++ /dev/null @@ -1,151 +0,0 @@ -/** - * The MIT License - * - * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF 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.apache; - -import kong.unirest.*; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; - -import java.io.Closeable; -import java.util.Optional; -import java.util.function.Function; -import java.util.stream.Stream; - -public class ApacheClient extends BaseApacheClient implements Client { - private final HttpClient client; - private final Config config; - private final PoolingHttpClientConnectionManager manager; - private final SyncIdleConnectionMonitorThread syncMonitor; - private final SecurityConfig security; - private boolean hookset; - - public ApacheClient(Config config) { - this.config = config; - security = new SecurityConfig(config); - manager = security.createManager(); - syncMonitor = new SyncIdleConnectionMonitorThread(manager); - syncMonitor.start(); - - HttpClientBuilder cb = HttpClients.custom() - .setDefaultRequestConfig(RequestOptions.toRequestConfig(config)) - .setDefaultCredentialsProvider(toApacheCreds(config.getProxy())) - .setConnectionManager(manager) - .useSystemProperties(); - - setOptions(cb); - client = cb.build(); - } - - - private void setOptions(HttpClientBuilder cb) { - security.configureSecurity(cb); - if (!config.isAutomaticRetries()) { - cb.disableAutomaticRetries(); - } - if (!config.isRequestCompressionOn()) { - cb.disableContentCompression(); - } - if (config.useSystemProperties()) { - cb.useSystemProperties(); - } - if (!config.getFollowRedirects()) { - cb.disableRedirectHandling(); - } - if (!config.getEnabledCookieManagement()) { - cb.disableCookieManagement(); - } - config.getInterceptors().stream().forEach(cb::addInterceptorFirst); - if (config.shouldAddShutdownHook()) { - registerShutdownHook(); - } - } - - @Override - public void registerShutdownHook() { - if(!hookset) { - hookset = true; - Runtime.getRuntime().addShutdownHook(new Thread(this::close, "Unirest Apache Client Shutdown Hook")); - } - } - - public ApacheClient(HttpClient httpClient, Config config, PoolingHttpClientConnectionManager clientManager, SyncIdleConnectionMonitorThread connMonitor) { - this.client = httpClient; - this.security = new SecurityConfig(config); - this.config = config; - this.manager = clientManager; - this.syncMonitor = connMonitor; - } - - - @Override - public HttpResponse request(HttpRequest request, Function> transformer) { - - HttpRequestBase requestObj = new RequestPrep(request, config, false).prepare(); - MetricContext metric = config.getMetric().begin(request.toSummary()); - try { - org.apache.http.HttpResponse execute = client.execute(requestObj); - ApacheResponse t = new ApacheResponse(execute, config); - metric.complete(t.toSummary(), null); - HttpResponse httpResponse = transformBody(transformer, t); - requestObj.releaseConnection(); - return httpResponse; - } catch (Exception e) { - metric.complete(null, e); - throw new UnirestException(e); - } finally { - requestObj.releaseConnection(); - } - } - - @Override - public HttpClient getClient() { - return client; - } - - public PoolingHttpClientConnectionManager getManager() { - return manager; - } - - public SyncIdleConnectionMonitorThread getSyncMonitor() { - return syncMonitor; - } - - @Override - public Stream close() { - return Util.collectExceptions(Util.tryCast(client, CloseableHttpClient.class) - .map(c -> Util.tryDo(c, Closeable::close)) - .filter(Optional::isPresent) - .map(Optional::get), - Util.tryDo(manager, m -> m.close()), - Util.tryDo(syncMonitor, i -> i.interrupt()) - ); - } - -} diff --git a/unirest/src/main/java/kong/unirest/apache/ApachePatchWithBody.java b/unirest/src/main/java/kong/unirest/apache/ApachePatchWithBody.java deleted file mode 100644 index 1399c4dbf..000000000 --- a/unirest/src/main/java/kong/unirest/apache/ApachePatchWithBody.java +++ /dev/null @@ -1,43 +0,0 @@ -/** - * The MIT License - * - * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF 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.apache; - -import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; - -import java.net.URI; - -class ApachePatchWithBody extends HttpEntityEnclosingRequestBase { - private static final String METHOD_NAME = "PATCH"; - - public String getMethod() { - return METHOD_NAME; - } - - ApachePatchWithBody(final String uri) { - super(); - setURI(URI.create(uri)); - } -} diff --git a/unirest/src/main/java/kong/unirest/apache/AsyncIdleConnectionMonitorThread.java b/unirest/src/main/java/kong/unirest/apache/AsyncIdleConnectionMonitorThread.java deleted file mode 100644 index 60da87e44..000000000 --- a/unirest/src/main/java/kong/unirest/apache/AsyncIdleConnectionMonitorThread.java +++ /dev/null @@ -1,66 +0,0 @@ -/** - * The MIT License - * - * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF 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.apache; - -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 - } - } - - public synchronized void tryStart() { - if(!super.isAlive()){ - super.start(); - } - } - -} diff --git a/unirest/src/main/java/kong/unirest/apache/BaseApacheClient.java b/unirest/src/main/java/kong/unirest/apache/BaseApacheClient.java deleted file mode 100644 index 402c88043..000000000 --- a/unirest/src/main/java/kong/unirest/apache/BaseApacheClient.java +++ /dev/null @@ -1,67 +0,0 @@ -/** - * The MIT License - * - * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF 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.apache; - -import kong.unirest.BasicResponse; -import kong.unirest.HttpResponse; -import kong.unirest.Proxy; -import kong.unirest.RawResponse; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.CredentialsProvider; -import org.apache.http.impl.client.BasicCredentialsProvider; - -import java.util.function.Function; - -abstract class BaseApacheClient { - - protected CredentialsProvider toApacheCreds(Proxy proxy) { - if(proxy != null && proxy.isAuthenticated()) { - CredentialsProvider proxyCreds = new BasicCredentialsProvider(); - proxyCreds.setCredentials(new AuthScope(proxy.getHost(), proxy.getPort()), - new UsernamePasswordCredentials(proxy.getUsername(), proxy.getPassword())); - return proxyCreds; - } - return null; - } - - protected HttpResponse transformBody(Function> transformer, RawResponse rr) { - try { - return transformer.apply(rr); - }catch (RuntimeException e){ - String 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/apache/MonitoringStreamBody.java b/unirest/src/main/java/kong/unirest/apache/MonitoringStreamBody.java deleted file mode 100644 index a26c3957e..000000000 --- a/unirest/src/main/java/kong/unirest/apache/MonitoringStreamBody.java +++ /dev/null @@ -1,68 +0,0 @@ -/** - * The MIT License - * - * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF 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.apache; - -import kong.unirest.ProgressMonitor; -import kong.unirest.UnirestException; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.mime.content.InputStreamBody; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Objects; - -class MonitoringStreamBody extends InputStreamBody { - private final ProgressMonitor monitor; - private final long length; - private final String name; - private final String fileName; - - public MonitoringStreamBody(InputStream in, - ContentType contentType, - String fileName, - String fieldName, - ProgressMonitor monitor) { - super(in, contentType, fileName); - this.fileName = fileName; - try { - this.monitor = monitor; - this.name = fieldName; - this.length = in.available(); - }catch (IOException e){ - throw new UnirestException(e); - } - } - - @Override - public void writeTo(OutputStream out) throws IOException { - if(Objects.nonNull(monitor)){ - super.writeTo(new MonitoringStream(out, length, name, fileName, monitor)); - } else { - super.writeTo(out); - } - } -} diff --git a/unirest/src/main/java/kong/unirest/apache/RequestPrep.java b/unirest/src/main/java/kong/unirest/apache/RequestPrep.java deleted file mode 100644 index 0b92c453c..000000000 --- a/unirest/src/main/java/kong/unirest/apache/RequestPrep.java +++ /dev/null @@ -1,136 +0,0 @@ -/** - * The MIT License - * - * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF 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.apache; - -import kong.unirest.*; -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.*; -import org.apache.http.message.BasicHeader; -import org.apache.http.nio.entity.NByteArrayEntity; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Function; - -class RequestPrep { - 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/3.0.00"; - private static final Map> FACTORIES; - private final HttpRequest request; - private Config config; - private final boolean async; - - static { - FACTORIES = new HashMap<>(); - FACTORIES.put(HttpMethod.GET, HttpGet::new); - FACTORIES.put(HttpMethod.POST, HttpPost::new); - FACTORIES.put(HttpMethod.PUT, HttpPut::new); - FACTORIES.put(HttpMethod.DELETE, ApacheDeleteWithBody::new); - FACTORIES.put(HttpMethod.PATCH, ApachePatchWithBody::new); - FACTORIES.put(HttpMethod.OPTIONS, HttpOptions::new); - FACTORIES.put(HttpMethod.HEAD, HttpHead::new); - } - - RequestPrep(HttpRequest request, Config config, boolean async) { - this.request = request; - this.config = config; - this.async = async; - } - - HttpRequestBase prepare() { - HttpRequestBase reqObj = getHttpRequestBase(); - - setBody(reqObj); - - return reqObj; - } - - private HttpRequestBase getHttpRequestBase() { - if (!request.getHeaders().containsKey(USER_AGENT_HEADER)) { - request.header(USER_AGENT_HEADER, USER_AGENT); - } - if (!request.getHeaders().containsKey(ACCEPT_ENCODING_HEADER) && config.isRequestCompressionOn()) { - request.header(ACCEPT_ENCODING_HEADER, "gzip"); - } - - try { - String url = request.getUrl(); - HttpRequestBase reqObj = FACTORIES.computeIfAbsent(request.getHttpMethod(), this::register).apply(url); - request.getHeaders().all().stream().map(this::toEntries).forEach(reqObj::addHeader); - reqObj.setConfig(overrideConfig()); - return reqObj; - } catch (RuntimeException e) { - throw new UnirestException(e); - } - } - - private RequestConfig overrideConfig() { - return RequestConfig.custom() - .setConnectTimeout(request.getConnectTimeout()) - .setSocketTimeout(request.getSocketTimeout()) - .setNormalizeUri(false) - .setConnectionRequestTimeout(request.getSocketTimeout()) - .setProxy(RequestOptions.toApacheProxy(request.getProxy())) - .setCookieSpec(config.getCookieSpec()) - .build(); - } - - private Function register(HttpMethod method) { - return u -> new ApacheRequestWithBody(method, u); - } - - private Header toEntries(kong.unirest.Header k) { - return new BasicHeader(k.getName(), k.getValue()); - } - - private void setBody(HttpRequestBase reqObj) { - if (request.getBody().isPresent()) { - ApacheBodyMapper mapper = new ApacheBodyMapper(request); - HttpEntity entity = mapper.apply(); - 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 UnirestException(e); - } - } else { - ((HttpEntityEnclosingRequestBase) reqObj).setEntity(entity); - } - } - } -} diff --git a/unirest/src/main/java/kong/unirest/apache/SecurityConfig.java b/unirest/src/main/java/kong/unirest/apache/SecurityConfig.java deleted file mode 100644 index 7cb8117fe..000000000 --- a/unirest/src/main/java/kong/unirest/apache/SecurityConfig.java +++ /dev/null @@ -1,139 +0,0 @@ -/** - * The MIT License - * - * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF 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.apache; - -import kong.unirest.Config; -import kong.unirest.UnirestConfigException; -import org.apache.http.config.Registry; -import org.apache.http.config.RegistryBuilder; -import org.apache.http.conn.socket.ConnectionSocketFactory; -import org.apache.http.conn.socket.PlainConnectionSocketFactory; -import org.apache.http.conn.ssl.NoopHostnameVerifier; -import org.apache.http.conn.ssl.SSLConnectionSocketFactory; -import org.apache.http.conn.ssl.TrustStrategy; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; -import org.apache.http.ssl.SSLContextBuilder; -import org.apache.http.ssl.SSLContexts; - -import javax.net.ssl.SSLContext; -import java.security.KeyManagementException; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.util.Optional; - -class SecurityConfig { - private final Config config; - private SSLContext sslContext; - private SSLConnectionSocketFactory sslSocketFactory; - - - public SecurityConfig(Config config) { - this.config = config; - } - - public PoolingHttpClientConnectionManager createManager() { - PoolingHttpClientConnectionManager manager = buildSocketFactory() - .map(PoolingHttpClientConnectionManager::new) - .orElseGet(PoolingHttpClientConnectionManager::new); - manager.setMaxTotal(config.getMaxConnections()); - manager.setDefaultMaxPerRoute(config.getMaxPerRoutes()); - return manager; - } - - private Optional> buildSocketFactory() { - try { - if (!config.isVerifySsl()) { - return Optional.of(createDisabledSSLContext()); - } else if (config.getKeystore() != null) { - return Optional.of(createCustomSslContext()); - } - } catch (Exception e) { - throw new UnirestConfigException(e); - } - - return Optional.empty(); - - } - private Registry createCustomSslContext() { - SSLConnectionSocketFactory socketFactory = getSocketFactory(); - return RegistryBuilder.create() - .register("https", socketFactory) - .register("http", PlainConnectionSocketFactory.INSTANCE) - .build(); - } - - private SSLConnectionSocketFactory getSocketFactory() { - if(sslSocketFactory == null) { - sslSocketFactory = new SSLConnectionSocketFactory(createSslContext(), new NoopHostnameVerifier()); - } - return sslSocketFactory; - } - - private Registry createDisabledSSLContext() throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException { - return RegistryBuilder.create() - .register("http", PlainConnectionSocketFactory.INSTANCE) - .register("https", new SSLConnectionSocketFactory(new SSLContextBuilder() - .loadTrustMaterial(null, (x509CertChain, authType) -> true) - .build(), - NoopHostnameVerifier.INSTANCE)) - .build(); - } - private SSLContext createSslContext() { - if(sslContext == null) { - try { - char[] pass = Optional.ofNullable(config.getKeyStorePassword()) - .map(String::toCharArray) - .orElse(null); - sslContext = SSLContexts.custom() - .loadKeyMaterial(config.getKeystore(), pass) - .build(); - } catch (Exception e) { - throw new UnirestConfigException(e); - } - } - return sslContext; - } - - public void configureSecurity(HttpClientBuilder cb) { - if(config.getKeystore() != null){ - cb.setSSLContext(createSslContext()); - cb.setSSLSocketFactory(getSocketFactory()); - } - if (!config.isVerifySsl()) { - disableSsl(cb); - } - } - - private void disableSsl(HttpClientBuilder cb) { - try { - cb.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE); - cb.setSSLContext(new SSLContextBuilder().loadTrustMaterial(null, (TrustStrategy) (arg0, arg1) -> true).build()); - } catch (Exception e) { - throw new UnirestConfigException(e); - } - } -} diff --git a/unirest/src/main/java/kong/unirest/apache/Util.java b/unirest/src/main/java/kong/unirest/apache/Util.java deleted file mode 100644 index 96c7daedf..000000000 --- a/unirest/src/main/java/kong/unirest/apache/Util.java +++ /dev/null @@ -1,68 +0,0 @@ -/** - * The MIT License - * - * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF 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.apache; - -import java.util.Objects; -import java.util.Optional; -import java.util.stream.Stream; - -class Util { - static Optional tryCast(T original, Class too) { - if (original != null && too.isAssignableFrom(original.getClass())) { - return Optional.of((M) original); - } - return Optional.empty(); - } - - static Stream collectExceptions(Optional... ex) { - return Stream.of(ex).flatMap(Util::stream); - } - - //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 Optional tryDo(T c, ExConsumer consumer) { - try { - if (Objects.nonNull(c)) { - consumer.accept(c); - } - return Optional.empty(); - } catch (Exception e) { - return Optional.of(e); - } - } - - @FunctionalInterface - public interface ExConsumer{ - void accept(T t) throws Exception; - } - - static boolean isNullOrEmpty(String s) { - return s == null || s.trim().isEmpty(); - } -} 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/BaseResponse.java b/unirest/src/main/java/kong/unirest/core/BaseResponse.java similarity index 61% rename from unirest/src/main/java/kong/unirest/BaseResponse.java rename to unirest/src/main/java/kong/unirest/core/BaseResponse.java index 55de9c225..10f41a1a7 100644 --- a/unirest/src/main/java/kong/unirest/BaseResponse.java +++ b/unirest/src/main/java/kong/unirest/core/BaseResponse.java @@ -23,8 +23,9 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; +import java.nio.charset.StandardCharsets; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; @@ -34,21 +35,29 @@ 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){ + + 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){ + 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 @@ -75,13 +84,13 @@ public Optional getParsingError() { } @Override - public V mapBody(Function func){ + public V mapBody(Function func) { return func.apply(getBody()); } @Override public HttpResponse map(Function func) { - return new MappedResponse(this, mapBody(func)); + return new BasicResponse(this, mapBody(func)); } protected void setParsingException(String originalBody, RuntimeException e) { @@ -95,7 +104,7 @@ public boolean isSuccess() { @Override public HttpResponse ifSuccess(Consumer> consumer) { - if(isSuccess()){ + if (isSuccess()) { consumer.accept(this); } return this; @@ -103,7 +112,7 @@ public HttpResponse ifSuccess(Consumer> consumer) { @Override public HttpResponse ifFailure(Consumer> consumer) { - if(!isSuccess()){ + if (!isSuccess()) { consumer.accept(this); } return this; @@ -111,22 +120,63 @@ public HttpResponse ifFailure(Consumer> consumer) { @Override public E mapError(Class errorClass) { - if(!isSuccess()){ + if (!isSuccess()) { + String errorBody = getErrorBody(); + if(String.class.equals(errorClass)){ + return (E) errorBody; + } try { - return config.getObjectMapper().readValue(getParsingError().get().getOriginalBody(), errorClass); - }catch (RuntimeException e) { - setParsingException(getParsingError().get().getOriginalBody(), e); + 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()){ + if (!isSuccess()) { E error = mapError(errorClass); - consumer.accept(new SimpleResponse(error, this)); + 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/BasicResponse.java b/unirest/src/main/java/kong/unirest/core/BasicResponse.java similarity index 83% rename from unirest/src/main/java/kong/unirest/BasicResponse.java rename to unirest/src/main/java/kong/unirest/core/BasicResponse.java index 5b086c86e..2ad4f6a07 100644 --- a/unirest/src/main/java/kong/unirest/BasicResponse.java +++ b/unirest/src/main/java/kong/unirest/core/BasicResponse.java @@ -23,16 +23,26 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +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); @@ -42,4 +52,9 @@ public BasicResponse(RawResponse httpResponse, String ogBody, RuntimeException e public T getBody() { return body; } + + @Override + protected String getRawBody() { + return null; + } } diff --git a/unirest/src/main/java/kong/unirest/Body.java b/unirest/src/main/java/kong/unirest/core/Body.java similarity index 85% rename from unirest/src/main/java/kong/unirest/Body.java rename to unirest/src/main/java/kong/unirest/core/Body.java index 7f62319dc..0e61c613a 100644 --- a/unirest/src/main/java/kong/unirest/Body.java +++ b/unirest/src/main/java/kong/unirest/core/Body.java @@ -23,7 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; import java.nio.charset.Charset; @@ -56,4 +56,15 @@ default ProgressMonitor getMonitor(){ return null; } + default String getBoundary() { + return null; + } + + 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/BodyPart.java b/unirest/src/main/java/kong/unirest/core/BodyPart.java similarity index 98% rename from unirest/src/main/java/kong/unirest/BodyPart.java rename to unirest/src/main/java/kong/unirest/core/BodyPart.java index 35cc47029..1d9bdb935 100644 --- a/unirest/src/main/java/kong/unirest/BodyPart.java +++ b/unirest/src/main/java/kong/unirest/core/BodyPart.java @@ -23,7 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; import java.nio.charset.StandardCharsets; @@ -63,7 +63,7 @@ public String getName() { } public String getFileName(){ - return null; + return name; } @Override diff --git a/unirest/src/main/java/kong/unirest/ByteArrayPart.java b/unirest/src/main/java/kong/unirest/core/ByteArrayPart.java similarity index 88% rename from unirest/src/main/java/kong/unirest/ByteArrayPart.java rename to unirest/src/main/java/kong/unirest/core/ByteArrayPart.java index 7d6eed2b0..6988d007a 100644 --- a/unirest/src/main/java/kong/unirest/ByteArrayPart.java +++ b/unirest/src/main/java/kong/unirest/core/ByteArrayPart.java @@ -23,10 +23,10 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; -public class ByteArrayPart extends BodyPart { +public class ByteArrayPart extends BodyPart { private final String fileName; ByteArrayPart(String name, byte[] bytes, ContentType contentType, String fileName) { @@ -43,4 +43,9 @@ public String getFileName() { 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/Callback.java b/unirest/src/main/java/kong/unirest/core/Callback.java similarity index 98% rename from unirest/src/main/java/kong/unirest/Callback.java rename to unirest/src/main/java/kong/unirest/core/Callback.java index 6b969f42d..5c99a85c0 100644 --- a/unirest/src/main/java/kong/unirest/Callback.java +++ b/unirest/src/main/java/kong/unirest/core/Callback.java @@ -23,7 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; public interface Callback { void completed(HttpResponse response); diff --git a/unirest/src/main/java/kong/unirest/CallbackFuture.java b/unirest/src/main/java/kong/unirest/core/CallbackFuture.java similarity index 98% rename from unirest/src/main/java/kong/unirest/CallbackFuture.java rename to unirest/src/main/java/kong/unirest/core/CallbackFuture.java index dbe0d7c72..c063953a2 100644 --- a/unirest/src/main/java/kong/unirest/CallbackFuture.java +++ b/unirest/src/main/java/kong/unirest/core/CallbackFuture.java @@ -23,7 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; import java.util.concurrent.CompletableFuture; diff --git a/unirest/src/main/java/kong/unirest/AsyncClient.java b/unirest/src/main/java/kong/unirest/core/Client.java similarity index 52% rename from unirest/src/main/java/kong/unirest/AsyncClient.java rename to unirest/src/main/java/kong/unirest/core/Client.java index 84829fc7a..b1cede72a 100644 --- a/unirest/src/main/java/kong/unirest/AsyncClient.java +++ b/unirest/src/main/java/kong/unirest/core/Client.java @@ -23,47 +23,66 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; - +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; -public interface AsyncClient { +/** + * The client that does the work. + */ +public interface Client { /** * @param the underlying client - * @return the underlying client if this instance is wrapping another library (like Apache). - */ + * @return the underlying client if this instance is wrapping another library. + */ T getClient(); + /** + * Make a request + * @param The type of the body + * @param request the prepared request object + * @param transformer the function to transform the response + * @param resultType the final body result type. This is a hint to downstream systems to make up for type erasure. + * @return a HttpResponse with a transformed body + */ + HttpResponse request(HttpRequest request, Function> transformer, Class resultType); + /** * Make a Async request * @param The type of the body * @param request the prepared request object * @param transformer the function to transform the response * @param callback the CompletableFuture that will handle the eventual response + * @param resultType the final body result type. This is a hint to downstream systems to make up for type erasure. * @return a CompletableFuture of a response */ - CompletableFuture> request(HttpRequest request, Function> transformer, CompletableFuture> callback); + CompletableFuture> request(HttpRequest request, + Function> transformer, + CompletableFuture> callback, + Class resultType); /** - * @return a stream of exceptions possibly thrown while closing all the things. + * Create a websocket connection + * @param request the connection + * @param listener (in the voice of Cicero) the listener + * @return a WebSocketResponse */ - default Stream close() { - return Stream.empty(); - } + WebSocketResponse websocket(WebSocketRequest request, WebSocket.Listener listener); - /** - * @return is the client running? - */ - default boolean isRunning() { - return true; - } /** - * Register the Async clients with shutdown hooks + * execute a SSE Event connection. + * Because these events are a stream they are processed async and take a handler you can use to consume the events + * @param request the request details + * @param handler the SseHandler + * @return a CompletableFuture */ - void registerShutdownHook(); + CompletableFuture sse(SseRequest request, SseHandler handler); + + 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..60bbf0e8b --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/Config.java @@ -0,0 +1,968 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF 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; + +public class Config { + public static final int DEFAULT_CONNECT_TIMEOUT = 10000; + public static final String JDK_HTTPCLIENT_KEEPALIVE_TIMEOUT = "jdk.httpclient.keepalive.timeout"; + 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; + + 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; + } + + /** + * Clear default headers + * @return this config object + */ + public Config clearDefaultHeaders() { + headers.clear(); + return this; + } + + /** + * Default basic auth credentials + * @param username the username + * @param password the password + * @return this config object + */ + 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; + } + + /** + * Allow the client to follow redirects. Defaults to TRUE + * + * @param enable The name of the header. + * @return this config object + */ + public Config followRedirects(boolean enable) { + validateClientsNotRunning(); + this.followRedirects = enable; + return this; + } + + /** + * Allow the client to manage cookies. Defaults to TRUE + * + * @param enable The name of the header. + * @return this config object + */ + public Config enableCookieManagement(boolean enable) { + validateClientsNotRunning(); + this.cookieManagement = enable; + return this; + } + + /** + * Toggle verifying SSL/TLS certificates. Defaults to TRUE + * + * @param value a bool is its true or not. + * @return this config object + */ + public Config verifySsl(boolean value) { + this.verifySsl = value; + return this; + } + + /** + * Tell the HttpClients to use the system properties for things like proxies + * + * @param value a bool is its true or not. + * @return this config object + */ + public Config useSystemProperties(boolean value) { + this.useSystemProperties = value; + return this; + } + + /** + * Turn on or off requesting all content as compressed. (GZIP encoded) + * Default is true + * + * @param value a bool is its true or not. + * @return this config object + */ + public Config requestCompression(boolean value) { + this.requestCompressionOn = value; + return this; + } + + /** + * Sets a cookie policy + * Acceptable values: + * 'default' (same as Netscape), + * 'netscape', + * 'ignoreCookies', + * 'standard' (RFC 6265 interoprability profile) , + * 'standard-strict' (RFC 6265 strict profile) + * + * @param policy: the policy for cookies to follow + * @return this config object + */ + public Config cookieSpec(String policy) { + this.cookieSpec = policy; + return this; + } + + /** + * Enable Response Caching with default options + * @param value enable or disable response caching + * @return this config object + */ + public Config cacheResponses(boolean value) { + if(value){ + this.cache = new CacheManager(); + } else { + this.cache = null; + } + return this; + } + + /** + * Enable Response Caching with custom options + * @param value enable or disable response caching + * @return this config object + */ + public Config cacheResponses(Cache.Builder value) { + this.cache = value.build(); + return this; + } + + /** + * Set the default encoding that will be used for serialization into Strings. + * The default-default is UTF-8 + * + * @param value a bool is its true or not. + * @return this config object + */ + public Config setDefaultResponseEncoding(String value) { + Objects.requireNonNull(value, "Encoding cannot be null"); + this.defaultResponseEncoding = value; + return this; + } + + /** + * Sets the jdk.httpclient.keepalive.timeout setting + * https://docs.oracle.com/en/java/javase/20/docs/api/java.net.http/module-summary.html + * The number of seconds to keep idle HTTP connections alive in the keep alive cache. + * This property applies to both HTTP/1.1 and HTTP/2. + * + * @param duration of ttl. + * @param unit the time unit of the ttl + * @return this config object + */ + 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 jdk.httpclient.keepalive.timeout setting + * https://docs.oracle.com/en/java/javase/20/docs/api/java.net.http/module-summary.html + * The number of seconds to keep idle HTTP connections alive in the keep alive cache. + * This property applies to both HTTP/1.1 and HTTP/2. + * + * @param duration of ttl. + * @return this config object + */ + public Config connectionTTL(Duration duration){ + return connectionTTL(duration.toMillis(), TimeUnit.MILLISECONDS); + } + + /** + * Automatically retry synchronous requests on 429/529 responses with the Retry-After response header + * Default is false + * + * @param value a bool is its true or not. + * @return this config object + */ + public Config retryAfter(boolean value) { + return retryAfter(value, 10); + } + + /** + * Automatically retry synchronous requests on 429/529 responses with the Retry-After response header + * Default is false + * + * @param value a bool is its true or not. + * @param maxRetryAttempts max retry attempts + * @return this config object + */ + public Config retryAfter(boolean value, int maxRetryAttempts) { + if(value) { + this.retry = new RetryStrategy.Standard(maxRetryAttempts); + } else { + this.retry = null; + } + return this; + } + + /** + * Automatically retry synchronous requests on 429/529 responses with the Retry-After response header + * Default is false + * + * @param strategy a RetryStrategy + * @return this config object + */ + public Config retryAfter(RetryStrategy strategy) { + this.retry = strategy; + return this; + } + + /** + * Requests a specific HTTP protocol version where possible. + * + * This is a direct proxy setter for the Java Http-Client that powers unirest. + * + *

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

If set to {@linkplain HttpClient.Version#HTTP_2 HTTP/2}, then each request + * will attempt to upgrade to HTTP/2. If the upgrade succeeds, then the + * response to this request will use HTTP/2 and all subsequent requests + * and responses to the same + * origin server + * will use HTTP/2. If the upgrade fails, then the response will be + * handled using HTTP/1.1 + * + * Constraints may also affect the selection of protocol version. + * For example, if HTTP/2 is requested through a proxy, and if the implementation + * does not support this mode, then HTTP/1.1 may be used + * + * @param value the requested HTTP protocol version + * @return this config + */ + public Config version(HttpClient.Version value) { + Objects.requireNonNull(value); + version = value; + return this; + } + + /** + * set a default base url for all routes. + * this is overridden if the url contains a valid base already + * the url may contain path params + * + * for example. Setting a default path of 'http://somwhere' + * and then calling Unirest with Unirest.get('/place') + * will result in a path of 'https://somwehre/place' + * @param value the base URL to use + * @return this config object + */ + public Config defaultBaseUrl(String value) { + this.defaultBaseUrl = value; + return this; + } + + /** + * Return default headers that are added to every request + * + * @return Headers + */ + public Headers getDefaultHeaders() { + return headers; + } + + /** + * Does the config have currently running clients? Find out here. + * + * @return boolean + */ + public boolean isRunning() { + return client.isPresent(); + } + + /** + * Shutdown the current config and re-init. + * + * @return this config + */ + public Config reset() { + reset(false); + return this; + } + + + /** + * Shut down the configuration and its clients. + * The config can be re-initialized with its settings + * + * @param clearOptions should the current non-client settings be retained. + */ + public void reset(boolean clearOptions) { + client = Optional.empty(); + + if (clearOptions) { + setDefaults(); + } + } + + /** + * Return the current Client. One will be build if it does + * not yet exist. + * + * @return A synchronous Client + */ + 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)); + } + } + + /** + * @return if cookie management should be enabled. + * default: true + */ + public boolean getEnabledCookieManagement() { + return cookieManagement; + } + + /** + * @return if the clients should follow redirects + * default: true + */ + public boolean getFollowRedirects() { + return followRedirects; + } + + /** + * @return the connection timeout in milliseconds + * default: 10000 + */ + public int getConnectionTimeout() { + return connectionTimeout; + } + + /** + * @return the connection timeout in milliseconds + * default: null (infinite) + */ + public Integer getRequestTimeout() { + return requestTimeout; + } + + /** + * @return a security keystore if one has been provided + */ + public KeyStore getKeystore() { + return this.keystore; + } + + /** + * @return The password for the keystore if provided + */ + public String getKeyStorePassword() { + return this.keystorePassword.get(); + } + + /** + * @return a configured object mapper + * @throws UnirestException if none has been configured. + */ + 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." + ); + } + } + + /** + * @return the configured proxy configuration + */ + public Proxy getProxy() { + return proxy; + } + + /** + * @return if the system will pick up system properties (default is false) + */ + public boolean useSystemProperties() { + return this.useSystemProperties; + } + + /** + * @return the default encoding (UTF-8 is the default default) + */ + public String getDefaultResponseEncoding() { + return defaultResponseEncoding; + } + + /** + * @return if request compression is on (default is true) + */ + 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; + } + + /** + * @return the configured Cookie Spec + */ + public String getCookieSpec() { + return cookieSpec; + } + + /** + * @return the currently configured UniMetric object + */ + public UniMetric getMetric() { + return metrics; + } + + /** + * @return the currently configured Interceptor + */ + public Interceptor getUniInterceptor() { + return interceptor; + } + + /** + * @return the SSL connection configuration + */ + public SSLContext getSslContext() { + return sslContext; + } + + /** + * @return the ciphers for the SSL connection configuration + */ + public String[] getCiphers() { + return ciphers; + } + + /** + * @return the protocols for the SSL connection configuration + */ + public String[] getProtocols() { + return protocols; + } + + /** + * @return the default base URL + */ + public String getDefaultBaseUrl() { + return this.defaultBaseUrl; + } + + /** + * @return the custom executor + */ + public Executor getCustomExecutor(){ + return customExecutor; + } + + /** + * @return the preferred http version + */ + public HttpClient.Version getVersion() { + return version; + } + /** + * @return if unirest will retry requests on 429/529 + */ + public boolean isAutomaticRetryAfter(){ + return retry != null; + } + + /** + * @return the max number of times to attempt to do a 429/529 retry-after + */ + public int maxRetries() { + return retry.getMaxAttempts(); + } + + /** + * @return the maximum life span of persistent connections regardless of their expiration setting. + */ + public long getTTL() { + try { + return Long.parseLong(System.getProperty(JDK_HTTPCLIENT_KEEPALIVE_TIMEOUT)); + }catch (NumberFormatException e){ + return -1; + } + } + + /** + * @return the RetryStrategy configured + */ + 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 a authenticator object for the client + * @param auth + * @return this config + */ + public Config authenticator(Authenticator auth) { + this.authenticator = auth; + return this; + } + + /** + * @return the authenticator registered with the config + */ + public Authenticator getAuthenticator(){ + return authenticator; + } + + /** + * @return the ProxySelector + */ + public ProxySelector getProxySelector(){ + return proxySelector; + } +} diff --git a/unirest/src/main/java/kong/unirest/ContentType.java b/unirest/src/main/java/kong/unirest/core/ContentType.java similarity index 71% rename from unirest/src/main/java/kong/unirest/ContentType.java rename to unirest/src/main/java/kong/unirest/core/ContentType.java index 4fb98e9c5..7959c9d5a 100644 --- a/unirest/src/main/java/kong/unirest/ContentType.java +++ b/unirest/src/main/java/kong/unirest/core/ContentType.java @@ -23,50 +23,73 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +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; public class ContentType { + private static final Set BINARY_TYPES = new HashSet<>(); + public static final ContentType APPLICATION_ATOM_XML = create("application/atom+xml", ISO_8859_1); public static final ContentType APPLICATION_FORM_URLENCODED = create("application/x-www-form-urlencoded", ISO_8859_1); public static final ContentType APPLICATION_JSON = create("application/json", StandardCharsets.UTF_8); public static final ContentType APPLICATION_JSON_PATCH = create("application/json-patch+json"); - public static final ContentType APPLICATION_OCTET_STREAM = create("application/octet-stream"); + public static final ContentType APPLICATION_OCTET_STREAM = create("application/octet-stream", true); + public static final ContentType BINARY_OCTET_STREAM = create("binary/octet-stream", true); public static final ContentType APPLICATION_SVG_XML = create("application/svg+xml", ISO_8859_1); public static final ContentType APPLICATION_XHTML_XML = create("application/xhtml+xml", ISO_8859_1); public static final ContentType APPLICATION_XML = create("application/xml", ISO_8859_1); - public static final ContentType IMAGE_BMP = create("image/bmp"); - public static final ContentType IMAGE_GIF = create("image/gif"); - public static final ContentType IMAGE_JPEG = create("image/jpeg"); - public static final ContentType IMAGE_PNG = create("image/png"); + public static final ContentType APPLICATION_PDF = create("application/pdf", true); + public static final ContentType IMAGE_BMP = create("image/bmp", true); + public static final ContentType IMAGE_GIF = create("image/gif", true); + public static final ContentType IMAGE_JPEG = create("image/jpeg", true); + public static final ContentType IMAGE_PNG = create("image/png", true); public static final ContentType IMAGE_SVG = create("image/svg+xml"); - public static final ContentType IMAGE_TIFF = create("image/tiff"); - public static final ContentType IMAGE_WEBP = create("image/webp"); + public static final ContentType IMAGE_TIFF = create("image/tiff", true); + public static final ContentType IMAGE_WEBP = create("image/webp", true); public static final ContentType MULTIPART_FORM_DATA = create("multipart/form-data", ISO_8859_1); public static final ContentType TEXT_HTML = create("text/html", ISO_8859_1); public static final ContentType TEXT_PLAIN = create("text/plain", ISO_8859_1); public static final ContentType TEXT_XML = create("text/xml", ISO_8859_1); + public static final ContentType EVENT_STREAMS = create("text/event-stream", StandardCharsets.UTF_8); public static final ContentType WILDCARD = create("*/*"); - private final String mimeType; + private final String mimeType; private final Charset encoding; - + private final boolean isBinary; public static ContentType create(String mimeType) { - return new ContentType(mimeType, null); + return new ContentType(mimeType, null, false); } public static ContentType create(String mimeType, Charset charset) { - return new ContentType(mimeType, charset); + return new ContentType(mimeType, charset, false); } - ContentType(String mimeType, Charset encoding) { + public static ContentType create(String mimeType, boolean isBinary) { + return new ContentType(mimeType, null, isBinary); + } + + 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); + } + } + + public static boolean isBinary(String mimeType) { + if(mimeType == null){ + return false; + } + String lc = mimeType.toLowerCase(); + return lc.contains("binary") || BINARY_TYPES.contains(lc); } @Override @@ -83,6 +106,14 @@ public String getMimeType() { } public ContentType withCharset(Charset charset) { - return new ContentType(mimeType, charset); + return new ContentType(mimeType, charset, isBinary); + } + + public boolean isBinary() { + return isBinary; + } + + 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..799a54a27 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/Cookie.java @@ -0,0 +1,304 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF 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 header + * per https://tools.ietf.org/html/rfc6265 + * + * note that the RFC is awful. + * The wikipedia article is far easier to understand https://en.wikipedia.org/wiki/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; + + public Cookie(String name, String value){ + this.name = name; + this.value = value; + } + + /** + * Construct a cookie from a set-cookie value + * @param v cookie string value + */ + 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); + } + + @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(";")); + } + + public void setDomain(String domain) { + this.domain = domain; + } + + public void setPath(String path) { + this.path = path; + } + + public void setHttpOnly(boolean httpOnly) { + this.httpOnly = httpOnly; + } + + public boolean isPartitioned() { + return this.partitioned; + } + + public void setPartitioned(boolean partitionedFlag) { + this.partitioned = partitionedFlag; + } + + 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; + } + + public enum SameSite { + None, Strict, Lax; + + private static EnumSet all = EnumSet.allOf(SameSite.class); + + 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/apache/SyncIdleConnectionMonitorThread.java b/unirest/src/main/java/kong/unirest/core/CookieSpecs.java similarity index 57% rename from unirest/src/main/java/kong/unirest/apache/SyncIdleConnectionMonitorThread.java rename to unirest/src/main/java/kong/unirest/core/CookieSpecs.java index 61a9f049b..af604e7cf 100644 --- a/unirest/src/main/java/kong/unirest/apache/SyncIdleConnectionMonitorThread.java +++ b/unirest/src/main/java/kong/unirest/core/CookieSpecs.java @@ -23,38 +23,38 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest.apache; - -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 - } - } +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/Empty.java b/unirest/src/main/java/kong/unirest/core/Empty.java similarity index 97% rename from unirest/src/main/java/kong/unirest/Empty.java rename to unirest/src/main/java/kong/unirest/core/Empty.java index ac0d109bd..aa769e82e 100644 --- a/unirest/src/main/java/kong/unirest/Empty.java +++ b/unirest/src/main/java/kong/unirest/core/Empty.java @@ -23,7 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +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/FilePart.java b/unirest/src/main/java/kong/unirest/core/FilePart.java similarity index 88% rename from unirest/src/main/java/kong/unirest/FilePart.java rename to unirest/src/main/java/kong/unirest/core/FilePart.java index 7a7a1ae09..0d0a50c95 100644 --- a/unirest/src/main/java/kong/unirest/FilePart.java +++ b/unirest/src/main/java/kong/unirest/core/FilePart.java @@ -23,11 +23,11 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; import java.io.File; -class FilePart extends BodyPart { +public class FilePart extends BodyPart { private String fileName; public FilePart(File file, String name) { @@ -46,6 +46,11 @@ public boolean isFile() { @Override public String getFileName(){ - return null; + return this.fileName; + } + + @Override + public String toString() { + return String.format("%s=%s", getName(), fileName); } } diff --git a/unirest/src/main/java/kong/unirest/FileResponse.java b/unirest/src/main/java/kong/unirest/core/FileResponse.java similarity index 67% rename from unirest/src/main/java/kong/unirest/FileResponse.java rename to unirest/src/main/java/kong/unirest/core/FileResponse.java index 6073a040c..e45cc1847 100644 --- a/unirest/src/main/java/kong/unirest/FileResponse.java +++ b/unirest/src/main/java/kong/unirest/core/FileResponse.java @@ -23,10 +23,11 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; import java.io.File; -import java.io.IOException; +import java.io.InputStream; +import java.nio.file.CopyOption; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -34,19 +35,32 @@ public class FileResponse extends BaseResponse { private File body; - public FileResponse(RawResponse r, String path) { + public FileResponse(RawResponse r, String path, ProgressMonitor downloadMonitor, CopyOption... copyOptions) { super(r); try { Path target = Paths.get(path); - Files.copy(r.getContent(), target); + InputStream content = getContent(r, downloadMonitor, target); + Files.copy(content, target, copyOptions); body = target.toFile(); - } catch (IOException e) { - throw new UnirestException(e); + } 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/GenericType.java b/unirest/src/main/java/kong/unirest/core/GenericType.java similarity index 66% rename from unirest/src/main/java/kong/unirest/GenericType.java rename to unirest/src/main/java/kong/unirest/core/GenericType.java index bfde9a56d..2a73b39b2 100644 --- a/unirest/src/main/java/kong/unirest/GenericType.java +++ b/unirest/src/main/java/kong/unirest/core/GenericType.java @@ -23,23 +23,33 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +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 - -*/ +/** + * 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; @@ -52,6 +62,9 @@ protected GenericType() { } } + /** + * @return the Type which includes generic type information + */ public Type getType() { return this.type; } @@ -59,4 +72,8 @@ public Type getType() { public int compareTo(GenericType o) { return 0; } + + public Class getTypeClass(){ + return this.type.getClass(); + } } diff --git a/unirest/src/main/java/kong/unirest/GetRequest.java b/unirest/src/main/java/kong/unirest/core/GetRequest.java similarity index 97% rename from unirest/src/main/java/kong/unirest/GetRequest.java rename to unirest/src/main/java/kong/unirest/core/GetRequest.java index b6585f9ad..7f3639822 100644 --- a/unirest/src/main/java/kong/unirest/GetRequest.java +++ b/unirest/src/main/java/kong/unirest/core/GetRequest.java @@ -23,7 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; public interface GetRequest extends HttpRequest { } diff --git a/unirest/src/main/java/kong/unirest/Header.java b/unirest/src/main/java/kong/unirest/core/Header.java similarity index 97% rename from unirest/src/main/java/kong/unirest/Header.java rename to unirest/src/main/java/kong/unirest/core/Header.java index 7c86dff06..fe61c6260 100644 --- a/unirest/src/main/java/kong/unirest/Header.java +++ b/unirest/src/main/java/kong/unirest/core/Header.java @@ -23,7 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; public interface Header { String getName(); diff --git a/unirest/src/main/java/kong/unirest/HeaderNames.java b/unirest/src/main/java/kong/unirest/core/HeaderNames.java similarity index 99% rename from unirest/src/main/java/kong/unirest/HeaderNames.java rename to unirest/src/main/java/kong/unirest/core/HeaderNames.java index f27a66250..ab21cf2a4 100644 --- a/unirest/src/main/java/kong/unirest/HeaderNames.java +++ b/unirest/src/main/java/kong/unirest/core/HeaderNames.java @@ -23,7 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; /** * Constants enumerating the HTTP headers. All headers defined in RFC1945 (HTTP/1.0), RFC2616 (HTTP/1.1), and RFC2518 diff --git a/unirest/src/main/java/kong/unirest/Headers.java b/unirest/src/main/java/kong/unirest/core/Headers.java similarity index 56% rename from unirest/src/main/java/kong/unirest/Headers.java rename to unirest/src/main/java/kong/unirest/core/Headers.java index a935cd65f..23b758ac3 100644 --- a/unirest/src/main/java/kong/unirest/Headers.java +++ b/unirest/src/main/java/kong/unirest/core/Headers.java @@ -23,17 +23,17 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Objects; +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; @@ -76,7 +76,11 @@ public void replace(String name, String value) { add(name, value); } - private void remove(String name) { + /** + * Remove a header by name + * @param name the name of the header + */ + public void remove(String name) { headers.removeIf(h -> isName(h, name)); } @@ -143,13 +147,103 @@ public String getFirst(String key) { * @return all the headers, in order */ public List
all() { - return this.headers; + 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; @@ -162,7 +256,7 @@ public Entry(String name, String value) { public Entry(String name, Supplier value) { this.name = name; - this.value = value; + this.value = value == null ? () -> null : value; } @Override @@ -172,7 +266,30 @@ public String getName() { @Override public String getValue() { - return value.get(); + 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/HttpMethod.java b/unirest/src/main/java/kong/unirest/core/HttpMethod.java similarity index 92% rename from unirest/src/main/java/kong/unirest/HttpMethod.java rename to unirest/src/main/java/kong/unirest/core/HttpMethod.java index a1d09cbfb..4e789c3fc 100644 --- a/unirest/src/main/java/kong/unirest/HttpMethod.java +++ b/unirest/src/main/java/kong/unirest/core/HttpMethod.java @@ -23,7 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; import java.util.HashMap; import java.util.HashSet; @@ -41,6 +41,7 @@ public class HttpMethod { public static final HttpMethod HEAD = valueOf("HEAD"); public static final HttpMethod OPTIONS = valueOf("OPTIONS"); public static final HttpMethod TRACE = valueOf("TRACE"); + public static final HttpMethod WEBSOCKET = valueOf("WEBSOCKET"); private final String name; @@ -49,7 +50,7 @@ private HttpMethod(String name){ } public static HttpMethod valueOf(String verb){ - return REGISTRY.computeIfAbsent(verb, HttpMethod::new); + return REGISTRY.computeIfAbsent(String.valueOf(verb).toUpperCase(), HttpMethod::new); } public Set all(){ diff --git a/unirest/src/main/java/kong/unirest/HttpRequest.java b/unirest/src/main/java/kong/unirest/core/HttpRequest.java similarity index 80% rename from unirest/src/main/java/kong/unirest/HttpRequest.java rename to unirest/src/main/java/kong/unirest/core/HttpRequest.java index d262bf0ff..4f8914252 100644 --- a/unirest/src/main/java/kong/unirest/HttpRequest.java +++ b/unirest/src/main/java/kong/unirest/core/HttpRequest.java @@ -23,11 +23,15 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +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; @@ -41,7 +45,7 @@ public interface HttpRequest { /** * add a route param that replaces the matching {name} * For example routeParam("name", "fred") will replace {name} in - * https://localhost/users/{user} + * https://localhost/users/{name} * to * https://localhost/users/fred * @@ -54,7 +58,7 @@ public interface HttpRequest { /** * 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} + * https://localhost/users/{name} * to * https://localhost/users/fred * @@ -72,7 +76,17 @@ public interface HttpRequest { R basicAuth(String username, String password); /** - * The Accept heder to send (e.g. application/json + * 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 */ @@ -108,6 +122,35 @@ public interface HttpRequest { */ 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 @@ -142,26 +185,26 @@ public interface HttpRequest { R withObjectMapper(ObjectMapper mapper); /** - * Set a socket timeout for this request + * Set a timeout for this request * @param millies the time in millies * @return this request builder */ - R socketTimeout(int millies); + R requestTimeout(int millies); /** - * Set a connect timeout for this request - * @param millies the time in 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 connectTimeout(int millies); + R downloadMonitor(ProgressMonitor monitor); + /** - * Set a proxy for this request. Only basic proxies are supported. - * @param host the host url - * @param port the proxy port + * sets the HTTP version for the request. This will override any global config + * @param version the version * @return this request builder */ - R proxy(String host, int port); + R version(HttpClient.Version version); /** * Executes the request and returns the response with the body mapped into a String @@ -182,6 +225,26 @@ public interface HttpRequest { */ 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 @@ -274,24 +337,27 @@ public interface HttpRequest { /** * Executes the request and writes the contents into a file * @param path The path to the file. - * @return a file containing the results + * @param copyOptions options specifying how the copy should be done + * @return a HttpResponse with the file containing the results */ - HttpResponse asFile(String path); + 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); + 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); + 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. @@ -308,7 +374,7 @@ PagedList asPaged(Function mappingFunction, * Executes the request and returns the response without parsing the body * @return the basic HttpResponse */ - HttpResponse asEmpty(); + HttpResponse asEmpty(); /** * Executes the request asynchronously and returns the response without parsing the body @@ -363,22 +429,23 @@ default Optional getBody(){ } /** - * @return socket timeout for this request + * @return the connect timeout for this request */ - int getSocketTimeout(); + Integer getRequestTimeout(); /** - * @return the connect timeout for this request + * @return a summary for the response, used in metrics */ - int getConnectTimeout(); + HttpRequestSummary toSummary(); /** - * @return the proxy for this request + * @return the instant the request object was created in UTC (not when it was sent). */ - Proxy getProxy(); + Instant getCreationTime(); /** - * @return a summary for the response, used in metrics + * gets the version for the request, or null if not set. + * @return the version */ - HttpRequestSummary toSummary(); + HttpClient.Version getVersion(); } diff --git a/unirest/src/main/java/kong/unirest/HttpRequestBody.java b/unirest/src/main/java/kong/unirest/core/HttpRequestBody.java similarity index 87% rename from unirest/src/main/java/kong/unirest/HttpRequestBody.java rename to unirest/src/main/java/kong/unirest/core/HttpRequestBody.java index 8f11d217f..f956322f8 100644 --- a/unirest/src/main/java/kong/unirest/HttpRequestBody.java +++ b/unirest/src/main/java/kong/unirest/core/HttpRequestBody.java @@ -23,10 +23,9 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; -import org.json.JSONArray; -import org.json.JSONObject; +import kong.unirest.core.json.JSONElement; import java.io.File; import java.io.InputStream; @@ -44,6 +43,11 @@ 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); @@ -106,34 +110,28 @@ public RequestBodyEntity body(String body) { } @Override - public RequestBodyEntity body(Object body) { - return body(config.getObjectMapper().writeValue(body)); + public RequestBodyEntity body(InputStream body) { + return new HttpRequestUniBody(this).body(body); } @Override - public RequestBodyEntity body(byte[] body) { + public RequestBodyEntity body(Object body) { return new HttpRequestUniBody(this).body(body); } - /** - * Sugar method for body operation - * - * @param body raw org.JSONObject - * @return RequestBodyEntity instance - */ @Override - public RequestBodyEntity body(JSONObject body) { - return body(body.toString()); + public RequestBodyEntity body(byte[] body) { + return new HttpRequestUniBody(this).body(body); } /** * Sugar method for body operation * - * @param body raw org.JSONArray + * @param body raw JSONElement * @return RequestBodyEntity instance */ @Override - public RequestBodyEntity body(JSONArray body) { + public RequestBodyEntity body(JSONElement body) { return body(body.toString()); } @@ -142,8 +140,10 @@ public Charset getCharset() { return charSet; } - void setCharset(Charset charset) { - this.charSet = charset; + @Override + public HttpRequestWithBody contentType(String type) { + headers.add("Content-Type", type); + return this; } @Override diff --git a/unirest/src/main/java/kong/unirest/HttpRequestJsonPatch.java b/unirest/src/main/java/kong/unirest/core/HttpRequestJsonPatch.java similarity index 90% rename from unirest/src/main/java/kong/unirest/HttpRequestJsonPatch.java rename to unirest/src/main/java/kong/unirest/core/HttpRequestJsonPatch.java index b696d7e21..89f01cac5 100644 --- a/unirest/src/main/java/kong/unirest/HttpRequestJsonPatch.java +++ b/unirest/src/main/java/kong/unirest/core/HttpRequestJsonPatch.java @@ -23,9 +23,8 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; -import java.nio.charset.StandardCharsets; import java.util.Optional; class HttpRequestJsonPatch extends BaseRequest implements JsonPatchRequest { @@ -37,6 +36,11 @@ class HttpRequestJsonPatch extends BaseRequest implements Json 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); @@ -81,7 +85,7 @@ public Optional getBody() { @Override public BodyPart uniPart() { String bodyAsString = items.toString(); - return new UnibodyString(bodyAsString, StandardCharsets.UTF_8); + return new UnibodyString(bodyAsString); } @Override @@ -93,4 +97,8 @@ public boolean isMultiPart() { 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/HttpRequestMultiPart.java b/unirest/src/main/java/kong/unirest/core/HttpRequestMultiPart.java similarity index 85% rename from unirest/src/main/java/kong/unirest/HttpRequestMultiPart.java rename to unirest/src/main/java/kong/unirest/core/HttpRequestMultiPart.java index 29d4987c1..8f6f9aaf9 100644 --- a/unirest/src/main/java/kong/unirest/HttpRequestMultiPart.java +++ b/unirest/src/main/java/kong/unirest/core/HttpRequestMultiPart.java @@ -23,9 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; - -import org.apache.http.HttpHeaders; +package kong.unirest.core; import java.io.File; import java.io.InputStream; @@ -39,12 +37,22 @@ class HttpRequestMultiPart extends BaseRequest implements Multipa 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(String name, String value) { addPart(new ParamPart(name, value)); @@ -57,6 +65,12 @@ public MultipartBody field(String name, String value, String contentType) { return this; } + @Override + public MultipartBody field(String name, String value, ContentType contentType) { + addPart(name, value, contentType.getMimeType()); + return this; + } + @Override public MultipartBody field(String name, Collection collection) { for (Object current: collection) { @@ -108,6 +122,12 @@ public MultipartBody field(String name, byte[] bytes, String fileName) { return this; } + @Override + public MultipartBody sortFields() { + Collections.sort(parameters); + return this; + } + @Override public MultipartBody charset(Charset charset) { this.charSet = charset; @@ -116,7 +136,7 @@ public MultipartBody charset(Charset charset) { @Override public MultipartBody contentType(String mimeType) { - header(HttpHeaders.CONTENT_TYPE, mimeType); + header(HeaderNames.CONTENT_TYPE, mimeType); return this; } @@ -132,6 +152,20 @@ public MultipartBody uploadMonitor(ProgressMonitor 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; @@ -167,7 +201,6 @@ private void addPart(String name, Object value, String contentType) { private void addPart(BodyPart value) { parameters.add(value); - Collections.sort(parameters); } @Override @@ -203,6 +236,7 @@ public ProgressMonitor getMonitor() { MultipartBody forceMultiPart(boolean value) { forceMulti = value; + headers.remove("Content-Type"); return this; } } diff --git a/unirest/src/main/java/kong/unirest/HttpRequestNoBody.java b/unirest/src/main/java/kong/unirest/core/HttpRequestNoBody.java similarity index 93% rename from unirest/src/main/java/kong/unirest/HttpRequestNoBody.java rename to unirest/src/main/java/kong/unirest/core/HttpRequestNoBody.java index 3eff9cc45..0f5f75cf2 100644 --- a/unirest/src/main/java/kong/unirest/HttpRequestNoBody.java +++ b/unirest/src/main/java/kong/unirest/core/HttpRequestNoBody.java @@ -23,7 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; import java.util.Optional; @@ -32,6 +32,10 @@ class HttpRequestNoBody extends BaseRequest implements GetRequest { 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/HttpRequestSummary.java b/unirest/src/main/java/kong/unirest/core/HttpRequestSummary.java similarity index 85% rename from unirest/src/main/java/kong/unirest/HttpRequestSummary.java rename to unirest/src/main/java/kong/unirest/core/HttpRequestSummary.java index 9da4ea9b6..94e5f0892 100644 --- a/unirest/src/main/java/kong/unirest/HttpRequestSummary.java +++ b/unirest/src/main/java/kong/unirest/core/HttpRequestSummary.java @@ -23,7 +23,9 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; + +import java.util.Collection; /** * A summary of a request about to be performed @@ -43,4 +45,15 @@ public interface HttpRequestSummary { * @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/HttpRequestUniBody.java b/unirest/src/main/java/kong/unirest/core/HttpRequestUniBody.java similarity index 63% rename from unirest/src/main/java/kong/unirest/HttpRequestUniBody.java rename to unirest/src/main/java/kong/unirest/core/HttpRequestUniBody.java index f7d398bb6..eb2237a35 100644 --- a/unirest/src/main/java/kong/unirest/HttpRequestUniBody.java +++ b/unirest/src/main/java/kong/unirest/core/HttpRequestUniBody.java @@ -23,8 +23,11 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; +import kong.unirest.core.json.JSONElement; + +import java.io.InputStream; import java.nio.charset.Charset; import java.util.Optional; @@ -32,20 +35,51 @@ class HttpRequestUniBody extends BaseRequest implements Reque 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, charSet); + this.body = new UnibodyString(bodyAsString); return this; } @@ -61,6 +95,18 @@ public RequestBodyEntity charset(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); @@ -85,4 +131,9 @@ public boolean isEntityBody() { 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..148e76f01 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/HttpRequestWithBody.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; + +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); + + /** + * 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/HttpResponse.java b/unirest/src/main/java/kong/unirest/core/HttpResponse.java similarity index 91% rename from unirest/src/main/java/kong/unirest/HttpResponse.java rename to unirest/src/main/java/kong/unirest/core/HttpResponse.java index f1ccd35c5..806ff29f7 100644 --- a/unirest/src/main/java/kong/unirest/HttpResponse.java +++ b/unirest/src/main/java/kong/unirest/core/HttpResponse.java @@ -23,7 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; import java.util.Optional; import java.util.function.Consumer; @@ -98,6 +98,7 @@ public interface HttpResponse { * 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 */ @@ -116,4 +117,15 @@ public interface HttpResponse { * @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/HttpResponseSummary.java b/unirest/src/main/java/kong/unirest/core/HttpResponseSummary.java similarity index 98% rename from unirest/src/main/java/kong/unirest/HttpResponseSummary.java rename to unirest/src/main/java/kong/unirest/core/HttpResponseSummary.java index e49a65297..bf6fb66aa 100644 --- a/unirest/src/main/java/kong/unirest/HttpResponseSummary.java +++ b/unirest/src/main/java/kong/unirest/core/HttpResponseSummary.java @@ -23,7 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; /** * A Summary of rhe response 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..b829be03b --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/HttpStatus.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.unirest.core; + +public final class HttpStatus { + public static final int OK = 200; + public static final int CREATED = 201; + public static final int ACCEPTED = 202; + public static final int NON_AUTHORITATIVE_INFORMATION = 203; + public static final int NO_CONTENT = 204; + public static final int RESET_CONTENT = 205; + public static final int PARTIAL_CONTENT = 206; + public static final int MULTI_STATUS = 207; + public static final int ALREADY_REPORTED = 208; + public static final int IM_USED = 226; + public static final int MULTIPLE_CHOICE = 300; + public static final int MOVED_PERMANENTLY = 301; + public static final int FOUND = 302; + public static final int SEE_OTHER = 303; + public static final int NOT_MODIFIED = 304; + public static final int USE_PROXY = 305; + public static final int UNUSED = 306; + public static final int TEMPORARY_REDIRECT = 307; + public static final int PERMANENT_REDIRECT = 308; + public static final int BAD_REQUEST = 400; + public static final int UNAUTHORIZED = 401; + public static final int PAYMENT_REQUIRED = 402; + public static final int FORBIDDEN = 403; + public static final int NOT_FOUND = 404; + public static final int METHOD_NOT_ALLOWED = 405; + public static final int NOT_ACCEPTABLE = 406; + public static final int PROXY_AUTHENTICATION_REQUIRED = 407; + public static final int REQUEST_TIMEOUT = 408; + public static final int CONFLICT = 409; + public static final int GONE = 410; + public static final int LENGTH_REQUIRED = 411; + public static final int PRECONDITION_FAILED = 412; + public static final int PAYLOAD_TOO_LARGE = 413; + public static final int URI_TOO_LONG = 414; + public static final int UNSUPPORTED_MEDIA_TYPE = 415; + public static final int RANGE_NOT_SATISFIABLE = 416; + public static final int EXPECTATION_FAILED = 417; + public static final int IM_A_TEAPOT = 418; + public static final int MISDIRECTED_REQUEST = 421; + public static final int UNPROCESSABLE_ENTITY = 422; + public static final int LOCKED = 423; + public static final int FAILED_DEPENDENCY = 424; + public static final int TOO_EARLY = 425; + public static final int UPGRADE_REQUIRED = 426; + public static final int PRECONDITION_REQUIRED = 428; + public static final int TOO_MANY_REQUESTS = 429; + public static final int REQUEST_HEADER_FIELDS_TOO_LARGE = 431; + public static final int UNAVAILABLE_FOR_LEGAL_REASONS = 451; + public static final int INTERNAL_SERVER_ERROR = 500; + public static final int NOT_IMPLEMENTED = 501; + public static final int BAD_GATEWAY = 502; + public static final int SERVICE_UNAVAILABLE = 503; + public static final int GATEWAY_TIMEOUT = 504; + public static final int VERSION_NOT_SUPPORTED = 505; + public static final int VARIANT_ALSO_NEGOTIATES = 506; + public static final int INSUFFICIENT_STORAGE = 507; + public static final int LOOP_DETECTED = 508; + public static final int NOT_EXTENDED = 510; + public static final int NETWORK_AUTHENTICATION_REQUIRED = 511; +} diff --git a/unirest/src/main/java/kong/unirest/SimpleResponse.java b/unirest/src/main/java/kong/unirest/core/InputStreamBody.java similarity index 83% rename from unirest/src/main/java/kong/unirest/SimpleResponse.java rename to unirest/src/main/java/kong/unirest/core/InputStreamBody.java index 21437d7d8..8005f5ebe 100644 --- a/unirest/src/main/java/kong/unirest/SimpleResponse.java +++ b/unirest/src/main/java/kong/unirest/core/InputStreamBody.java @@ -23,18 +23,17 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; -class SimpleResponse extends BaseResponse { - private final T body; +import java.io.InputStream; - SimpleResponse(T body, BaseResponse response) { - super(response); - this.body = body; +class InputStreamBody extends BodyPart { + public InputStreamBody(InputStream body) { + super(body, null, null); } @Override - public T getBody() { - return body; + public boolean isFile() { + return false; } } diff --git a/unirest/src/main/java/kong/unirest/InputStreamPart.java b/unirest/src/main/java/kong/unirest/core/InputStreamPart.java similarity index 89% rename from unirest/src/main/java/kong/unirest/InputStreamPart.java rename to unirest/src/main/java/kong/unirest/core/InputStreamPart.java index 2584a13e0..a91332522 100644 --- a/unirest/src/main/java/kong/unirest/InputStreamPart.java +++ b/unirest/src/main/java/kong/unirest/core/InputStreamPart.java @@ -23,17 +23,13 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; import java.io.InputStream; -public class InputStreamPart extends BodyPart { +public class InputStreamPart extends BodyPart { private String fileName; - InputStreamPart(String name, InputStream value) { - super(value, name, null); - } - InputStreamPart(String name, InputStream value, String contentType) { super(value, name, contentType); } @@ -52,4 +48,9 @@ public String getFileName() { 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/JsonNode.java b/unirest/src/main/java/kong/unirest/core/JsonNode.java similarity index 87% rename from unirest/src/main/java/kong/unirest/JsonNode.java rename to unirest/src/main/java/kong/unirest/core/JsonNode.java index 8ed927414..052321688 100644 --- a/unirest/src/main/java/kong/unirest/JsonNode.java +++ b/unirest/src/main/java/kong/unirest/core/JsonNode.java @@ -23,11 +23,11 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; +import kong.unirest.core.json.JSONArray; +import kong.unirest.core.json.JSONException; +import kong.unirest.core.json.JSONObject; public class JsonNode { @@ -75,4 +75,12 @@ public String toString() { 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/JsonPatch.java b/unirest/src/main/java/kong/unirest/core/JsonPatch.java similarity index 93% rename from unirest/src/main/java/kong/unirest/JsonPatch.java rename to unirest/src/main/java/kong/unirest/core/JsonPatch.java index 8c74568ee..89a9b67bf 100644 --- a/unirest/src/main/java/kong/unirest/JsonPatch.java +++ b/unirest/src/main/java/kong/unirest/core/JsonPatch.java @@ -23,12 +23,13 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; -import org.json.JSONArray; -import org.json.JSONObject; +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; @@ -77,7 +78,7 @@ public String toString() { return a.toString(); } - public Iterable getOperations(){ + public Collection getOperations(){ return Collections.unmodifiableList(items); } } \ No newline at end of file diff --git a/unirest/src/main/java/kong/unirest/JsonPatchItem.java b/unirest/src/main/java/kong/unirest/core/JsonPatchItem.java similarity index 95% rename from unirest/src/main/java/kong/unirest/JsonPatchItem.java rename to unirest/src/main/java/kong/unirest/core/JsonPatchItem.java index 9484d0558..3c1d5332c 100644 --- a/unirest/src/main/java/kong/unirest/JsonPatchItem.java +++ b/unirest/src/main/java/kong/unirest/core/JsonPatchItem.java @@ -23,9 +23,9 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; -import org.json.JSONObject; +import kong.unirest.core.json.JSONObject; import java.util.Objects; @@ -61,7 +61,7 @@ public boolean equals(Object o) { JsonPatchItem that = (JsonPatchItem) o; return op == that.op && Objects.equals(path, that.path) && - Objects.equals(toString(), that.toString()); + Objects.equals(value, that.value); } @Override diff --git a/unirest/src/main/java/kong/unirest/JsonPatchOperation.java b/unirest/src/main/java/kong/unirest/core/JsonPatchOperation.java similarity index 98% rename from unirest/src/main/java/kong/unirest/JsonPatchOperation.java rename to unirest/src/main/java/kong/unirest/core/JsonPatchOperation.java index 359c722c1..c7ab4818f 100644 --- a/unirest/src/main/java/kong/unirest/JsonPatchOperation.java +++ b/unirest/src/main/java/kong/unirest/core/JsonPatchOperation.java @@ -23,7 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; public enum JsonPatchOperation { add("value"), diff --git a/unirest/src/main/java/kong/unirest/JsonPatchRequest.java b/unirest/src/main/java/kong/unirest/core/JsonPatchRequest.java similarity index 98% rename from unirest/src/main/java/kong/unirest/JsonPatchRequest.java rename to unirest/src/main/java/kong/unirest/core/JsonPatchRequest.java index 86a1bccef..ba9eb6f01 100644 --- a/unirest/src/main/java/kong/unirest/JsonPatchRequest.java +++ b/unirest/src/main/java/kong/unirest/core/JsonPatchRequest.java @@ -23,7 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; public interface JsonPatchRequest extends HttpRequest, Body { String CONTENT_TYPE = "application/json-patch+json"; diff --git a/unirest/src/main/java/kong/unirest/JsonResponse.java b/unirest/src/main/java/kong/unirest/core/JsonResponse.java similarity index 94% rename from unirest/src/main/java/kong/unirest/JsonResponse.java rename to unirest/src/main/java/kong/unirest/core/JsonResponse.java index 1f6d6c720..d380f8468 100644 --- a/unirest/src/main/java/kong/unirest/JsonResponse.java +++ b/unirest/src/main/java/kong/unirest/core/JsonResponse.java @@ -23,7 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; import java.util.Objects; @@ -57,4 +57,9 @@ private JsonNode toJsonNode(String json) { public JsonNode getBody() { return node; } + + @Override + protected String getRawBody() { + return node.toString(); + } } diff --git a/unirest/src/main/java/kong/unirest/MetricContext.java b/unirest/src/main/java/kong/unirest/core/MetricContext.java similarity index 98% rename from unirest/src/main/java/kong/unirest/MetricContext.java rename to unirest/src/main/java/kong/unirest/core/MetricContext.java index 22b7c059d..6d1dde0b4 100644 --- a/unirest/src/main/java/kong/unirest/MetricContext.java +++ b/unirest/src/main/java/kong/unirest/core/MetricContext.java @@ -23,7 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; /** * A metric context for the current request 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/MultipartBody.java b/unirest/src/main/java/kong/unirest/core/MultipartBody.java similarity index 87% rename from unirest/src/main/java/kong/unirest/MultipartBody.java rename to unirest/src/main/java/kong/unirest/core/MultipartBody.java index 0132c7c79..76ccb68b2 100644 --- a/unirest/src/main/java/kong/unirest/MultipartBody.java +++ b/unirest/src/main/java/kong/unirest/core/MultipartBody.java @@ -23,7 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; import java.io.File; import java.io.InputStream; @@ -51,6 +51,15 @@ public interface MultipartBody extends HttpRequest, Body { */ 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 + */ + 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 @@ -123,6 +132,12 @@ public interface MultipartBody extends HttpRequest, Body { */ 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 @@ -151,4 +166,12 @@ public interface MultipartBody extends HttpRequest, Body { * @return The same MultipartBody * */ MultipartBody uploadMonitor(ProgressMonitor monitor); + + /** + * Sets the value to use as the boundary identifier. + * see https://datatracker.ietf.org/doc/html/rfc2046 + * @param boundaryIdentifier the value + * @return The same MultipartBody + */ + MultipartBody boundary(String boundaryIdentifier); } diff --git a/unirest/src/main/java/kong/unirest/MultipartMode.java b/unirest/src/main/java/kong/unirest/core/MultipartMode.java similarity index 98% rename from unirest/src/main/java/kong/unirest/MultipartMode.java rename to unirest/src/main/java/kong/unirest/core/MultipartMode.java index c289b6e47..57e1a888e 100644 --- a/unirest/src/main/java/kong/unirest/MultipartMode.java +++ b/unirest/src/main/java/kong/unirest/core/MultipartMode.java @@ -23,7 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; public enum MultipartMode { /** RFC 822, RFC 2045, RFC 2046 compliant */ diff --git a/unirest/src/main/java/kong/unirest/NoopMetric.java b/unirest/src/main/java/kong/unirest/core/NoopMetric.java similarity index 98% rename from unirest/src/main/java/kong/unirest/NoopMetric.java rename to unirest/src/main/java/kong/unirest/core/NoopMetric.java index 86a68cd40..7071c0d4d 100644 --- a/unirest/src/main/java/kong/unirest/NoopMetric.java +++ b/unirest/src/main/java/kong/unirest/core/NoopMetric.java @@ -23,7 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; class NoopMetric implements UniMetric { @Override 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/ObjectResponse.java b/unirest/src/main/java/kong/unirest/core/ObjectResponse.java similarity index 88% rename from unirest/src/main/java/kong/unirest/ObjectResponse.java rename to unirest/src/main/java/kong/unirest/core/ObjectResponse.java index 034f8d77c..1e6b39d11 100644 --- a/unirest/src/main/java/kong/unirest/ObjectResponse.java +++ b/unirest/src/main/java/kong/unirest/core/ObjectResponse.java @@ -23,7 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; import java.util.Optional; import java.util.function.Function; @@ -32,6 +32,7 @@ class ObjectResponse extends BaseResponse { private final T body; private final ObjectMapper om; + private String rawBody; ObjectResponse(ObjectMapper om, RawResponse response, Class to) { super(response); @@ -53,7 +54,11 @@ private Optional readBody(RawResponse response) { if(!response.hasContent()){ return Optional.empty(); } - return Optional.of(response.getContentAsString()); + String s = response.getContentAsString(); + if(response.getStatus() >= 400){ + rawBody = s; + } + return Optional.of(s); } private T getBody(String b, Function func){ @@ -69,4 +74,9 @@ private T getBody(String b, Function func){ public T getBody() { return body; } + + @Override + protected String getRawBody() { + return rawBody; + } } diff --git a/unirest/src/main/java/kong/unirest/PagedList.java b/unirest/src/main/java/kong/unirest/core/PagedList.java similarity index 98% rename from unirest/src/main/java/kong/unirest/PagedList.java rename to unirest/src/main/java/kong/unirest/core/PagedList.java index 0742a0155..82762b66a 100644 --- a/unirest/src/main/java/kong/unirest/PagedList.java +++ b/unirest/src/main/java/kong/unirest/core/PagedList.java @@ -23,7 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; import java.util.ArrayList; import java.util.List; diff --git a/unirest/src/main/java/kong/unirest/ParamPart.java b/unirest/src/main/java/kong/unirest/core/ParamPart.java similarity index 77% rename from unirest/src/main/java/kong/unirest/ParamPart.java rename to unirest/src/main/java/kong/unirest/core/ParamPart.java index 2ccaac1c3..ecbab2741 100644 --- a/unirest/src/main/java/kong/unirest/ParamPart.java +++ b/unirest/src/main/java/kong/unirest/core/ParamPart.java @@ -23,9 +23,12 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; -public class ParamPart extends BodyPart { +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; + +public class ParamPart extends BodyPart { ParamPart(String name, String value) { this(name, value, null); } @@ -38,4 +41,13 @@ public class ParamPart extends BodyPart { 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/Path.java b/unirest/src/main/java/kong/unirest/core/Path.java similarity index 53% rename from unirest/src/main/java/kong/unirest/Path.java rename to unirest/src/main/java/kong/unirest/core/Path.java index deeed3558..40f6263f4 100644 --- a/unirest/src/main/java/kong/unirest/Path.java +++ b/unirest/src/main/java/kong/unirest/core/Path.java @@ -23,46 +23,91 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +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 Path { +/** + * Class for building a URI with query params + */ +public class Path { private String url; private String rawPath; - Path(String url) { - this.url = url; - this.rawPath = url; + /** + * 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); - int count = 0; - while (matcher.find()) { - count++; - } - if (count == 0) { + if (!matcher.find()) { throw new UnirestException("Can't find route parameter name \"" + name + "\""); } - this.url = url.replaceAll("\\{" + name + "\\}", Util.encode(value)); + 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("?")) { @@ -81,6 +126,10 @@ public void queryString(String name, Object value) { 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()) { @@ -91,10 +140,48 @@ public void queryString(Map parameters) { @Override public String toString() { - return url; + 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/ProgressMonitor.java b/unirest/src/main/java/kong/unirest/core/ProgressMonitor.java similarity index 72% rename from unirest/src/main/java/kong/unirest/ProgressMonitor.java rename to unirest/src/main/java/kong/unirest/core/ProgressMonitor.java index 7b6b31e5b..2f9142d8d 100644 --- a/unirest/src/main/java/kong/unirest/ProgressMonitor.java +++ b/unirest/src/main/java/kong/unirest/core/ProgressMonitor.java @@ -23,25 +23,28 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; /** * A ProgressMonitor is a functional interface which can be passed to unirest for the purposes of - * monitoring file uploads. A common use case is for drawing upload progress bars. - * If the upload contains multiple files each one is called individually and the file name is provided. + * 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 could keep track of the total bytes of files you planned to upload and then + * 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 + * @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 so far - * @param totalBytes the total bytes that will be uploaded. Note this this may be an estimate if an InputStream was used + * @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/Proxy.java b/unirest/src/main/java/kong/unirest/core/Proxy.java similarity index 66% rename from unirest/src/main/java/kong/unirest/Proxy.java rename to unirest/src/main/java/kong/unirest/core/Proxy.java index d3ca8f5fc..d20064ef9 100644 --- a/unirest/src/main/java/kong/unirest/Proxy.java +++ b/unirest/src/main/java/kong/unirest/core/Proxy.java @@ -23,18 +23,36 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +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; @@ -42,22 +60,37 @@ public Proxy(String host, Integer port, String username, String password) { 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/RawResponseBase.java b/unirest/src/main/java/kong/unirest/core/RawResponseBase.java similarity index 89% rename from unirest/src/main/java/kong/unirest/RawResponseBase.java rename to unirest/src/main/java/kong/unirest/core/RawResponseBase.java index 66e0fbe9c..0af0c0887 100644 --- a/unirest/src/main/java/kong/unirest/RawResponseBase.java +++ b/unirest/src/main/java/kong/unirest/core/RawResponseBase.java @@ -23,7 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -31,10 +31,12 @@ 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){ + protected RawResponseBase(Config config, HttpRequestSummary summary){ this.config = config; + this.reqSummary = summary; } protected String getCharSet() { @@ -73,4 +75,9 @@ public Config getConfig() { 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/apache/RequestOptions.java b/unirest/src/main/java/kong/unirest/core/RequestFactory.java similarity index 56% rename from unirest/src/main/java/kong/unirest/apache/RequestOptions.java rename to unirest/src/main/java/kong/unirest/core/RequestFactory.java index 67989281e..11fffc742 100644 --- a/unirest/src/main/java/kong/unirest/apache/RequestOptions.java +++ b/unirest/src/main/java/kong/unirest/core/RequestFactory.java @@ -23,30 +23,26 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest.apache; +package kong.unirest.core; -import kong.unirest.Config; -import kong.unirest.Proxy; -import org.apache.http.HttpHost; -import org.apache.http.client.config.RequestConfig; - -class RequestOptions { - static RequestConfig toRequestConfig(Config config) { - Integer connectionTimeout = config.getConnectionTimeout(); - Integer socketTimeout = config.getSocketTimeout(); - HttpHost proxy = toApacheProxy(config.getProxy()); - return RequestConfig.custom() - .setConnectTimeout(connectionTimeout) - .setSocketTimeout(socketTimeout) - .setConnectionRequestTimeout(socketTimeout) - .setProxy(proxy) - .build(); - } - - public static HttpHost toApacheProxy(Proxy proxy){ - if(proxy == null){ - return null; +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); } - return new HttpHost(proxy.getHost(), proxy.getPort()); + 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/RequestSummary.java b/unirest/src/main/java/kong/unirest/core/RequestSummary.java similarity index 72% rename from unirest/src/main/java/kong/unirest/RequestSummary.java rename to unirest/src/main/java/kong/unirest/core/RequestSummary.java index 743112571..ef4d91720 100644 --- a/unirest/src/main/java/kong/unirest/RequestSummary.java +++ b/unirest/src/main/java/kong/unirest/core/RequestSummary.java @@ -23,31 +23,42 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; + +import java.util.Collection; +import java.util.List; class RequestSummary implements HttpRequestSummary { - private final String rawPath; - private final String url; - private final HttpMethod method; + private static final SummaryFormatter FORMATTER = new SummaryFormatter(); + private final BaseRequest request; + RequestSummary(BaseRequest request) { - this.url = request.getUrl(); - this.method = request.getHttpMethod(); - this.rawPath = request.getPath().rawPath(); + this.request = request; } @Override public HttpMethod getHttpMethod() { - return method; + return request.getHttpMethod(); } @Override public String getUrl() { - return url; + return request.getUrl(); } @Override public String getRawPath() { - return rawPath; + 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/ResponseSummary.java b/unirest/src/main/java/kong/unirest/core/ResponseSummary.java similarity index 98% rename from unirest/src/main/java/kong/unirest/ResponseSummary.java rename to unirest/src/main/java/kong/unirest/core/ResponseSummary.java index 5985b689a..cbe6150cc 100644 --- a/unirest/src/main/java/kong/unirest/ResponseSummary.java +++ b/unirest/src/main/java/kong/unirest/core/ResponseSummary.java @@ -23,7 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; class ResponseSummary implements HttpResponseSummary { private final int status; 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/RequestBodyEntity.java b/unirest/src/main/java/kong/unirest/core/SseHandler.java similarity index 68% rename from unirest/src/main/java/kong/unirest/RequestBodyEntity.java rename to unirest/src/main/java/kong/unirest/core/SseHandler.java index 8a82d4fc6..58d4c2308 100644 --- a/unirest/src/main/java/kong/unirest/RequestBodyEntity.java +++ b/unirest/src/main/java/kong/unirest/core/SseHandler.java @@ -23,16 +23,26 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; -import java.nio.charset.Charset; +import kong.unirest.core.java.Event; -public interface RequestBodyEntity extends HttpRequest, Body { - RequestBodyEntity body(byte[] bodyBytes); - - RequestBodyEntity body(String bodyAsString); - - RequestBodyEntity body(JsonNode jsonBody); +/** + * 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); - RequestBodyEntity charset(Charset charset); + /** + * 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/StringResponse.java b/unirest/src/main/java/kong/unirest/core/StringResponse.java similarity index 93% rename from unirest/src/main/java/kong/unirest/StringResponse.java rename to unirest/src/main/java/kong/unirest/core/StringResponse.java index 3ac78d207..9ae26f816 100644 --- a/unirest/src/main/java/kong/unirest/StringResponse.java +++ b/unirest/src/main/java/kong/unirest/core/StringResponse.java @@ -23,7 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; public class StringResponse extends BaseResponse { private String body; @@ -37,4 +37,9 @@ public StringResponse(RawResponse response, String encoding) { 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..0651c214c --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/SummaryFormatter.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; + +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()); + sj.add(""); + } else { + sj.add("Content-Disposition: form-data; name:\""+p.getName()+"\""); + sj.add("Content-Type: " + p.getContentType()); + sj.add(String.valueOf(p.getValue())); + } + sj.add(""); + }); + return sj.toString(); + } +} diff --git a/unirest/src/test/java/kong/unirest/GsonObjectMapper.java b/unirest/src/main/java/kong/unirest/core/TestBody.java similarity index 72% rename from unirest/src/test/java/kong/unirest/GsonObjectMapper.java rename to unirest/src/main/java/kong/unirest/core/TestBody.java index a56185163..3f681c7bc 100644 --- a/unirest/src/test/java/kong/unirest/GsonObjectMapper.java +++ b/unirest/src/main/java/kong/unirest/core/TestBody.java @@ -23,26 +23,32 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; -import com.google.gson.Gson; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; -public class GsonObjectMapper implements ObjectMapper { +public class TestBody implements Body { - private final Gson gson = new Gson(); + private final List parts; + + public TestBody(BodyPart... parts){ + this.parts = Arrays.asList(parts); + } @Override - public T readValue(String value, Class valueType) { - return gson.fromJson(value, valueType); + public Collection multiParts() { + return parts; } @Override - public T readValue(String value, GenericType genericType) { - return gson.fromJson(value, genericType.getType()); + public boolean isMultiPart() { + return true; } @Override - public String writeValue(Object value) { - return gson.toJson(value); + public boolean isEntityBody() { + return false; } } diff --git a/unirest/src/main/java/kong/unirest/UniByteArrayBody.java b/unirest/src/main/java/kong/unirest/core/UniByteArrayBody.java similarity index 86% rename from unirest/src/main/java/kong/unirest/UniByteArrayBody.java rename to unirest/src/main/java/kong/unirest/core/UniByteArrayBody.java index c8f3ce06b..24eb899dc 100644 --- a/unirest/src/main/java/kong/unirest/UniByteArrayBody.java +++ b/unirest/src/main/java/kong/unirest/core/UniByteArrayBody.java @@ -23,9 +23,9 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; -class UniByteArrayBody extends BodyPart { +class UniByteArrayBody extends BodyPart { UniByteArrayBody(byte[] bodyBytes) { super(bodyBytes, null, null); } @@ -34,4 +34,9 @@ class UniByteArrayBody extends BodyPart { 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/UniMetric.java b/unirest/src/main/java/kong/unirest/core/UniMetric.java similarity index 98% rename from unirest/src/main/java/kong/unirest/UniMetric.java rename to unirest/src/main/java/kong/unirest/core/UniMetric.java index 62afeeb7b..c48fec7b1 100644 --- a/unirest/src/main/java/kong/unirest/UniMetric.java +++ b/unirest/src/main/java/kong/unirest/core/UniMetric.java @@ -23,7 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; /** * A UniMetric is a factory for producing a MetricContext this will be called just before diff --git a/unirest/src/main/java/kong/unirest/UnibodyString.java b/unirest/src/main/java/kong/unirest/core/UnibodyString.java similarity index 87% rename from unirest/src/main/java/kong/unirest/UnibodyString.java rename to unirest/src/main/java/kong/unirest/core/UnibodyString.java index 7b5c052ed..adcd7fcf7 100644 --- a/unirest/src/main/java/kong/unirest/UnibodyString.java +++ b/unirest/src/main/java/kong/unirest/core/UnibodyString.java @@ -23,12 +23,10 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; -import java.nio.charset.Charset; - -class UnibodyString extends BodyPart { - UnibodyString(String bodyAsString, Charset charset) { +class UnibodyString extends BodyPart { + UnibodyString(String bodyAsString) { super(bodyAsString, null, null); } @@ -36,4 +34,9 @@ class UnibodyString extends BodyPart { public boolean isFile() { return false; } + + @Override + public String toString() { + return getValue(); + } } diff --git a/unirest/src/main/java/kong/unirest/Unirest.java b/unirest/src/main/java/kong/unirest/core/Unirest.java similarity index 95% rename from unirest/src/main/java/kong/unirest/Unirest.java rename to unirest/src/main/java/kong/unirest/core/Unirest.java index 0aa6537f8..5ca595ad0 100644 --- a/unirest/src/main/java/kong/unirest/Unirest.java +++ b/unirest/src/main/java/kong/unirest/core/Unirest.java @@ -23,7 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; public class Unirest { @@ -50,7 +50,7 @@ public static void shutDown() { * @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.shutDown(clearOptions); + primaryInstance.reset(clearOptions); } /** @@ -130,13 +130,12 @@ public static HttpRequestWithBody request(String method, String url) { return primaryInstance.request(method, url); } - /** - * Does the config have currently running clients? Find out here. - * - * @return boolean - */ - public static boolean isRunning() { - return primaryInstance.isRunning(); + public static WebSocketRequest webSocket(String url) { + return primaryInstance.webSocket(url); + } + + public static SseRequest sse(String url) { + return primaryInstance.sse(url); } /** diff --git a/unirest/src/main/java/kong/unirest/UnirestConfigException.java b/unirest/src/main/java/kong/unirest/core/UnirestConfigException.java similarity index 98% rename from unirest/src/main/java/kong/unirest/UnirestConfigException.java rename to unirest/src/main/java/kong/unirest/core/UnirestConfigException.java index cbad99d13..c1a28287a 100644 --- a/unirest/src/main/java/kong/unirest/UnirestConfigException.java +++ b/unirest/src/main/java/kong/unirest/core/UnirestConfigException.java @@ -23,7 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; public class UnirestConfigException extends UnirestException { public UnirestConfigException(Exception e){ diff --git a/unirest/src/main/java/kong/unirest/UnirestException.java b/unirest/src/main/java/kong/unirest/core/UnirestException.java similarity index 98% rename from unirest/src/main/java/kong/unirest/UnirestException.java rename to unirest/src/main/java/kong/unirest/core/UnirestException.java index 2dceef403..8cf243f6a 100644 --- a/unirest/src/main/java/kong/unirest/UnirestException.java +++ b/unirest/src/main/java/kong/unirest/core/UnirestException.java @@ -23,7 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; import java.util.Collection; import java.util.stream.Collectors; diff --git a/unirest/src/main/java/kong/unirest/UnirestInstance.java b/unirest/src/main/java/kong/unirest/core/UnirestInstance.java similarity index 93% rename from unirest/src/main/java/kong/unirest/UnirestInstance.java rename to unirest/src/main/java/kong/unirest/core/UnirestInstance.java index dfe74f606..e635a56de 100644 --- a/unirest/src/main/java/kong/unirest/UnirestInstance.java +++ b/unirest/src/main/java/kong/unirest/core/UnirestInstance.java @@ -23,7 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; /** * A Instance of the unirest runtime, you can have many of these but it is the config @@ -53,8 +53,8 @@ public Config 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 shutDown() { - shutDown(true); + public void reset() { + reset(true); } /** @@ -62,8 +62,8 @@ public void shutDown() { * * @param clearOptions indicates if options should be cleared. Note that the HttpClient, AsyncClient and thread monitors will not be retained after shutDown. */ - public void shutDown(boolean clearOptions) { - config.shutDown(clearOptions); + public void reset(boolean clearOptions) { + config.reset(clearOptions); } /** @@ -143,21 +143,21 @@ public HttpRequestWithBody request(String method, String url) { return new HttpRequestBody(config, HttpMethod.valueOf(method), url); } - /** - * Does the config have currently running clients? Find out here. - * - * @return boolean - */ - public boolean isRunning() { - return config.isRunning(); - } - /** * 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() { - shutDown(true); + reset(true); + } + + public WebSocketRequest webSocket(String url) { + return new WebSocketRequestImpl(config, url); + } + + public SseRequestImpl sse(String url) { + return new SseRequestImpl(config, url); } } diff --git a/unirest/src/main/java/kong/unirest/UnirestParsingException.java b/unirest/src/main/java/kong/unirest/core/UnirestParsingException.java similarity index 98% rename from unirest/src/main/java/kong/unirest/UnirestParsingException.java rename to unirest/src/main/java/kong/unirest/core/UnirestParsingException.java index 8046ed688..46dc70655 100644 --- a/unirest/src/main/java/kong/unirest/UnirestParsingException.java +++ b/unirest/src/main/java/kong/unirest/core/UnirestParsingException.java @@ -23,7 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; public class UnirestParsingException extends UnirestException { private final String 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/Util.java b/unirest/src/main/java/kong/unirest/core/Util.java similarity index 59% rename from unirest/src/main/java/kong/unirest/Util.java rename to unirest/src/main/java/kong/unirest/core/Util.java index ab892297c..91b06c701 100644 --- a/unirest/src/main/java/kong/unirest/Util.java +++ b/unirest/src/main/java/kong/unirest/core/Util.java @@ -23,17 +23,47 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; -import java.util.Base64; -import java.util.Optional; +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; -class Util { + +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) { @@ -41,7 +71,7 @@ static Stream stream(Optional opt) { } static String nullToEmpty(Object v) { - if(v == null){ + if (v == null) { return ""; } return String.valueOf(v); @@ -63,11 +93,23 @@ public static String toBasicAuthValue(String username, String password) { return "Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes()); } - static FileInputStream getFileInputStream(String location){ + 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..0f36fba23 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/java/BodyBuilder.java @@ -0,0 +1,198 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF 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", p.getName(), 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); + } 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); + } else { + builder.formPart(part.getName(), + new PartPublisher(HttpRequest.BodyPublishers.ofInputStream(() -> (InputStream) part.getValue()), contentType), contentType); + } + + } else if (part instanceof ByteArrayPart) { + builder.formPart(part.getName(), + standardizeName(part, o.getMode()), + new PartPublisher(HttpRequest.BodyPublishers.ofByteArray((byte[]) part.getValue()), contentType), contentType); + } + } else { + builder.textPart(part.getName(), String.valueOf(part.getValue()), contentType); + } + } + + 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/apache/ApacheResponse.java b/unirest/src/main/java/kong/unirest/core/java/JavaResponse.java similarity index 72% rename from unirest/src/main/java/kong/unirest/apache/ApacheResponse.java rename to unirest/src/main/java/kong/unirest/core/java/JavaResponse.java index 25a9cf94f..883841c97 100644 --- a/unirest/src/main/java/kong/unirest/apache/ApacheResponse.java +++ b/unirest/src/main/java/kong/unirest/core/java/JavaResponse.java @@ -23,53 +23,56 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest.apache; +package kong.unirest.core.java; -import kong.unirest.*; -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.util.EntityUtils; +import kong.unirest.core.*; import java.io.*; -import java.util.stream.Stream; +import java.net.http.HttpResponse; import java.util.zip.GZIPInputStream; -class ApacheResponse extends RawResponseBase { - private final HttpResponse r; +import static kong.unirest.core.HeaderNames.CONTENT_TYPE; - public ApacheResponse(HttpResponse r, Config config) { - super(config); - this.r = r; +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 r.getStatusLine().getStatusCode(); + return response.statusCode(); } @Override public String getStatusText() { - return r.getStatusLine().getReasonPhrase(); + return ""; } @Override public Headers getHeaders() { Headers h = new Headers(); - Stream.of(r.getAllHeaders()) - .forEachOrdered(e -> h.add(e.getName(), e.getValue())); + response.headers().map() + .entrySet() + .forEach(e -> { + e.getValue().forEach(v -> h.add(e.getKey(), v)); + }); return h; } @Override public InputStream getContent() { try { - HttpEntity entity = r.getEntity(); - if (entity != null) { - return entity.getContent(); + InputStream body = response.body(); + if (isGzipped(getEncoding()) && !(body instanceof GZIPInputStream)) { + body = new GZIPInputStream(body); } - return new ByteArrayInputStream(new byte[0]); - } catch (IOException e) { + return body; + } catch (EOFException e){ + return new ByteArrayInputStream(new byte[]{}); + } catch (Exception e){ throw new UnirestException(e); } } @@ -81,17 +84,40 @@ public byte[] getContentAsBytes() { } try { InputStream is = getContent(); - if (isGzipped(getEncoding())) { - is = new GZIPInputStream(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 { - EntityUtils.consumeQuietly(r.getEntity()); + is.close(); } } + private static boolean isGzipped(String value) { + return "gzip".equalsIgnoreCase(value.toLowerCase().trim()); + } + @Override public String getContentAsString() { return getContentAsString(null); @@ -124,56 +150,23 @@ public InputStreamReader getContentReader() { @Override public boolean hasContent() { - return r.getEntity() != null; + return response.body() != null; } @Override public String getContentType() { - if (hasContent()) { - Header contentType = r.getEntity().getContentType(); - if (contentType != null) { - return contentType.getValue(); - } - } - return ""; + return response.headers() + .firstValue(CONTENT_TYPE) + .orElse(""); } @Override public String getEncoding() { if (hasContent()) { - Header contentType = r.getEntity().getContentEncoding(); - if (contentType != null) { - return contentType.getValue(); - } + String s = response.headers().firstValue(HeaderNames.CONTENT_ENCODING) + .orElse(""); + return s; } return ""; } - - 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()); - } } diff --git a/unirest/src/main/java/kong/unirest/apache/MonitoringStream.java b/unirest/src/main/java/kong/unirest/core/java/MonitorWrapper.java similarity index 50% rename from unirest/src/main/java/kong/unirest/apache/MonitoringStream.java rename to unirest/src/main/java/kong/unirest/core/java/MonitorWrapper.java index 76e95de6a..e53e1499b 100644 --- a/unirest/src/main/java/kong/unirest/apache/MonitoringStream.java +++ b/unirest/src/main/java/kong/unirest/core/java/MonitorWrapper.java @@ -23,58 +23,75 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest.apache; +package kong.unirest.core.java; -import kong.unirest.ProgressMonitor; +import kong.unirest.core.ProgressMonitor; import java.io.IOException; -import java.io.OutputStream; +import java.io.InputStream; -class MonitoringStream extends OutputStream { +class MonitorWrapper extends InputStream { + private final InputStream in; + private volatile long totalNumBytesRead = 0; - private final OutputStream out; - private volatile long written = 0; - private long length; - private String fieldName; - private String fileName; private ProgressMonitor monitor; - public MonitoringStream(OutputStream out, long length, String fieldName, String fileName, ProgressMonitor monitor) { - this.out = out; - this.length = length; - this.fieldName = fieldName; - this.fileName = fileName; + public MonitorWrapper(InputStream value, ProgressMonitor monitor) { + this.in = value; this.monitor = monitor; } @Override - public void write(int b) throws IOException { - out.write(b); - written++; - monitor.accept(fieldName, fileName, written, length); + public int read() throws IOException { + return (int)updateProgress(in.read()); } @Override - public void write(byte[] b) throws IOException { - out.write(b); - written += b.length; - monitor.accept(fieldName, fileName, written, length); + public int read(byte[] b) throws IOException { + return (int)updateProgress(in.read(b)); } @Override - public void write(byte[] b, int off, int len) throws IOException { - out.write(b, off, len); - written += len; - monitor.accept(fieldName, fileName, written, length); + public int read(byte[] b, int off, int len) throws IOException { + return (int)updateProgress(in.read(b, off, len)); } @Override - public void flush() throws IOException { - out.flush(); + 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 { - out.close(); + 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..8778f4f6d --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/java/MultipartBodyPublisher.java @@ -0,0 +1,228 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF 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 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) { + requireNonNull(name, "name"); + requireNonNull(bodyPublisher, "body"); + parts.add(new Part(name, null, bodyPublisher, contentType)); + 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) { + requireNonNull(name, "name"); + requireNonNull(filename, "filename"); + requireNonNull(body, "body"); + parts.add(new Part(name, filename, body, contentType)); + 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) { + return textPart(name, value, UTF_8, contentType); + } + + /** + * 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) { + requireNonNull(name, "name"); + requireNonNull(value, "value"); + requireNonNull(charset, "charset"); + return formPart(name, BodyPublishers.ofString(value.toString(), charset), contentType); + } + + + + /** + * 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) + 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); + } + + /** + * 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..0f845694c --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/java/Part.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.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) { + requireNonNull(bodyPublisher, "bodyPublisher"); + this.fieldName = fieldName; + this.filename = filename; + this.headers = getFormHeaders(fieldName, filename, contentType); + 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) { + 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); + 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/apache/MonitoringFileBody.java b/unirest/src/main/java/kong/unirest/core/java/Prefetcher.java similarity index 54% rename from unirest/src/main/java/kong/unirest/apache/MonitoringFileBody.java rename to unirest/src/main/java/kong/unirest/core/java/Prefetcher.java index d8fd19b13..beda46456 100644 --- a/unirest/src/main/java/kong/unirest/apache/MonitoringFileBody.java +++ b/unirest/src/main/java/kong/unirest/core/java/Prefetcher.java @@ -23,37 +23,42 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest.apache; - -import kong.unirest.ProgressMonitor; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.mime.content.FileBody; - -import java.io.File; -import java.io.IOException; -import java.io.OutputStream; -import java.util.Objects; - -public class MonitoringFileBody extends FileBody { - private final String field; - private final ProgressMonitor monitor; - private long length; - private String name; - - public MonitoringFileBody(String field, File file, ContentType contentType, ProgressMonitor monitor) { - super(file, contentType); - this.field = field; - this.monitor = monitor; - this.length = file.length(); - this.name = file.getName(); +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; } - @Override - public void writeTo(OutputStream out) throws IOException { - if(Objects.nonNull(monitor)){ - super.writeTo(new MonitoringStream(out, length, field, name, monitor)); + 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 { - super.writeTo(out); + 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/apache/ApacheDeleteWithBody.java b/unirest/src/main/java/kong/unirest/core/java/SseRequestBuilder.java similarity index 66% rename from unirest/src/main/java/kong/unirest/apache/ApacheDeleteWithBody.java rename to unirest/src/main/java/kong/unirest/core/java/SseRequestBuilder.java index 13cb5d246..a29447402 100644 --- a/unirest/src/main/java/kong/unirest/apache/ApacheDeleteWithBody.java +++ b/unirest/src/main/java/kong/unirest/core/java/SseRequestBuilder.java @@ -23,21 +23,29 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest.apache; +package kong.unirest.core.java; -import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; +import kong.unirest.core.ContentType; +import kong.unirest.core.SseRequest; import java.net.URI; +import java.net.http.HttpRequest; -class ApacheDeleteWithBody extends HttpEntityEnclosingRequestBase { - private static final String METHOD_NAME = "DELETE"; +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(); + } - public String getMethod() { - return METHOD_NAME; - } - ApacheDeleteWithBody(final String uri) { - super(); - setURI(URI.create(uri)); - } } 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/apache/ApacheNoRedirectStrategy.java b/unirest/src/main/java/kong/unirest/core/json/Foo.java similarity index 67% rename from unirest/src/main/java/kong/unirest/apache/ApacheNoRedirectStrategy.java rename to unirest/src/main/java/kong/unirest/core/json/Foo.java index 7cc0db43f..4a316ce2a 100644 --- a/unirest/src/main/java/kong/unirest/apache/ApacheNoRedirectStrategy.java +++ b/unirest/src/main/java/kong/unirest/core/json/Foo.java @@ -23,22 +23,38 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest.apache; +package kong.unirest.core.json; -import org.apache.http.HttpRequest; -import org.apache.http.HttpResponse; -import org.apache.http.client.RedirectStrategy; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.protocol.HttpContext; -class ApacheNoRedirectStrategy implements RedirectStrategy { + +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 isRedirected(HttpRequest request, HttpResponse response, HttpContext context) { - return false; + 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 HttpUriRequest getRedirect(HttpRequest request, HttpResponse response, HttpContext context) { - return null; + public int hashCode() { + return Objects.hash(bar); } -} +} \ No newline at end of file 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..3b87f2cd0 --- /dev/null +++ b/unirest/src/main/java/kong/unirest/core/json/JsonEngine.java @@ -0,0 +1,108 @@ +/** + * The MIT License + * + * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF 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; + +public interface JsonEngine { + String toPrettyJson(Element obj); + String toJson(Element obj); + void toJson(Element obj, Writer sw); + void toPrettyJson(Element engineElement, Writer sw); + Element toJsonTree(java.lang.Object obj); + Object newEngineObject(); + Object newEngineObject(String string) throws JSONException; + Array newJsonArray(String jsonString) throws JSONException; + Array newJsonArray(Collection collection); + Array newEngineArray(); + T fromJson(Element obj, Class mapClass); + Primitive newJsonPrimitive(T enumValue); + Primitive newJsonPrimitive(String string); + Primitive newJsonPrimitive(Number number); + Primitive newJsonPrimitive(Boolean bool); + ObjectMapper getObjectMapper(); + String quote(java.lang.Object s); + + interface Element { + Object getAsJsonObject(); + boolean isJsonNull(); + Primitive getAsJsonPrimitive(); + Array getAsJsonArray(); + float getAsFloat(); + double getAsDouble(); + String getAsString(); + long getAsLong(); + int getAsInt(); + boolean getAsBoolean(); + BigInteger getAsBigInteger(); + BigDecimal getAsBigDecimal(); + Primitive getAsPrimitive(); + boolean isJsonArray(); + boolean isJsonPrimitive(); + boolean isJsonObject(); + T getEngineElement(); + } + + interface Array extends Element { + int size(); + Element get(int index); + Element remove(int index); + Element put(int index, Number number); + Element put(int index, String number); + Element put(int index, Boolean number); + void add(Element obj); + void set(int index, Element o); + void add(Number num); + void add(String str); + void add(Boolean bool); + String join(String token); + } + + interface Object extends Element { + int size(); + boolean has(String key); + Element get(String key); + void add(String key, Element value); + void addProperty(String key, Boolean value); + void addProperty(String key, String value); + void addProperty(String key, Number value); + void addProperty(String key, Element value); + void remove(String key); + void add(String key, E enumValue); + Set keySet(); + } + + interface Primitive extends Element { + boolean isBoolean(); + boolean isNumber(); + } +} diff --git a/unirest/src/test/java/BehaviorTests/Foo.java b/unirest/src/main/java/kong/unirest/core/json/NullObject.java similarity index 81% rename from unirest/src/test/java/BehaviorTests/Foo.java rename to unirest/src/main/java/kong/unirest/core/json/NullObject.java index 89acde880..fdb7d4a78 100644 --- a/unirest/src/test/java/BehaviorTests/Foo.java +++ b/unirest/src/main/java/kong/unirest/core/json/NullObject.java @@ -23,21 +23,21 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package BehaviorTests; +package kong.unirest.core.json; -import com.google.common.base.MoreObjects; - -public class Foo { - public String bar; - - public Foo(){ } - - public Foo(String bar) { - this.bar = bar; +class NullObject extends JSONObject { + @Override + public boolean equals(Object other) { + return null == other; } @Override public String toString() { - return MoreObjects.toStringHelper(this).add("bar",bar).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/BehaviorTests/AsJsonTest.java b/unirest/src/test/java/BehaviorTests/AsJsonTest.java deleted file mode 100644 index de1f9801e..000000000 --- a/unirest/src/test/java/BehaviorTests/AsJsonTest.java +++ /dev/null @@ -1,107 +0,0 @@ -/** - * The MIT License - * - * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF 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.*; -import org.junit.Test; - -import java.util.concurrent.CompletableFuture; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; - -public class AsJsonTest extends BddTest { - - @Test - public void whenNoBodyIsReturned() { - HttpResponse i = Unirest.get(MockServer.NOBODY).asJson(); - - assertEquals(200, i.getStatus()); - assertEquals("{}", i.getBody().toString()); - } - - @Test - public void canGetBinaryResponse() { - HttpResponse i = Unirest.get(MockServer.GET) - .queryString("foo", "bar") - .asJson(); - - assertJson(i); - } - - @Test - public void canGetBinaryResponseAsync() throws Exception { - CompletableFuture> r = Unirest.get(MockServer.GET) - .queryString("foo", "bar") - .asJsonAsync(); - - assertJson(r.get()); - } - - @Test - public void canGetBinaryResponseAsyncWithCallback() { - Unirest.get(MockServer.GET) - .queryString("foo", "bar") - .asJsonAsync(r -> { - assertJson(r); - asyncSuccess(); - }); - - assertAsync(); - } - - @Test - public void failureToReturnValidJsonWillResultInAnEmptyNode() { - HttpResponse response = Unirest.get(MockServer.INVALID_REQUEST).asJson(); - - assertEquals(400, response.getStatus()); - assertNull(response.getBody()); - UnirestParsingException ex = response.getParsingError().get(); - assertEquals("You did something bad", ex.getOriginalBody()); - assertEquals("org.json.JSONException: A JSONArray text must start with '[' at 1 [character 2 line 1]", - response.getParsingError().get().getMessage()); - - } - - @Test - public void failureToReturnValidJsonWillResultInAnEmptyNodeAsync() { - Unirest.get(MockServer.INVALID_REQUEST) - .asJsonAsync(new MockCallback<>(this, response -> { - assertEquals(400, response.getStatus()); - assertNull(response.getBody()); - assertEquals(null, response.getBody()); - assertEquals("org.json.JSONException: A JSONArray text must start with '[' at 1 [character 2 line 1]", - response.getParsingError().get().getMessage()); - - })); - - assertAsync(); - } - - private void assertJson(HttpResponse i) { - assertEquals("bar",i.getBody().getObject().getJSONObject("params").getJSONArray("foo").get(0)); - } -} diff --git a/unirest/src/test/java/BehaviorTests/AsObjectTest.java b/unirest/src/test/java/BehaviorTests/AsObjectTest.java deleted file mode 100644 index 7b107af57..000000000 --- a/unirest/src/test/java/BehaviorTests/AsObjectTest.java +++ /dev/null @@ -1,143 +0,0 @@ -/** - * The MIT License - * - * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF 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.*; -import org.junit.Assert; -import org.junit.Test; - -import static junit.framework.TestCase.assertEquals; -import static junit.framework.TestCase.assertNull; -import static junit.framework.TestCase.assertTrue; - -public class AsObjectTest extends BddTest { - - @Test - public void whenNoBodyIsReturned() { - HttpResponse i = Unirest.get(MockServer.NOBODY).asObject(RequestCapture.class); - - Assert.assertEquals(200, i.getStatus()); - Assert.assertEquals(null, i.getBody()); - } - - @Test - public void canGetObjectResponse() { - Unirest.get(MockServer.GET) - .queryString("foo", "bar") - .asObject(RequestCapture.class) - .getBody() - .assertParam("foo", "bar"); - } - - @Test - public void canGetObjectResponseAsync() throws Exception { - Unirest.get(MockServer.GET) - .queryString("foo", "bar") - .asObjectAsync(RequestCapture.class) - .get() - .getBody() - .assertParam("foo", "bar"); - } - - @Test - public void canGetObjectResponseAsyncWithCallback() { - Unirest.get(MockServer.GET) - .queryString("foo", "bar") - .asObjectAsync(RequestCapture.class, r -> { - RequestCapture cap = r.getBody(); - cap.assertParam("foo", "bar"); - asyncSuccess(); - }); - - assertAsync(); - } - - @Test - public void canPassAnObjectMapperAsPartOfARequest(){ - TestingMapper mapper = new TestingMapper(); - Unirest.get(MockServer.GET) - .queryString("foo", "bar") - .withObjectMapper(mapper) - .asObject(RequestCapture.class) - .getBody() - .assertParam("foo", "bar"); - - assertTrue(mapper.wasCalled); - } - - @Test - public void ifTheObjectMapperFailsReturnEmptyAndAddToParsingError() { - HttpResponse request = Unirest.get(MockServer.INVALID_REQUEST) - .asObject(RequestCapture.class); - - assertNull(request.getBody()); - assertTrue(request.getParsingError().isPresent()); - assertEquals("kong.unirest.UnirestException: com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'You': was expecting ('true', 'false' or 'null')\n" + - " at [Source: (String)\"You did something bad\"; line: 1, column: 4]", request.getParsingError().get().getMessage()); - assertEquals("You did something bad", request.getParsingError().get().getOriginalBody()); - - } - - @Test - public void ifTheObjectMapperFailsReturnEmptyAndAddToParsingErrorObGenericTypes() { - HttpResponse request = Unirest.get(MockServer.INVALID_REQUEST) - .asObject(new GenericType() {}); - - assertNull(request.getBody()); - assertTrue(request.getParsingError().isPresent()); - assertEquals("kong.unirest.UnirestException: com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'You': was expecting ('true', 'false' or 'null')\n" + - " at [Source: (String)\"You did something bad\"; line: 1, column: 4]", request.getParsingError().get().getMessage()); - assertEquals("You did something bad", request.getParsingError().get().getOriginalBody()); - } - - @Test - public void unirestExceptionsAreAlsoParseExceptions() { - HttpResponse request = Unirest.get(MockServer.INVALID_REQUEST) - .asObject(new GenericType() {}); - - assertNull(request.getBody()); - assertTrue(request.getParsingError().isPresent()); - assertEquals("kong.unirest.UnirestException: com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'You': was expecting ('true', 'false' or 'null')\n" + - " at [Source: (String)\"You did something bad\"; line: 1, column: 4]", request.getParsingError().get().getMessage()); - assertEquals("You did something bad", request.getParsingError().get().getOriginalBody()); - } - - public static class TestingMapper implements ObjectMapper { - public boolean wasCalled; - - @Override - public T readValue(String value, Class valueType) { - this.wasCalled = true; - return new JacksonObjectMapper().readValue(value, valueType); - } - - @Override - public String writeValue(Object value) { - return new Gson().toJson(value); - } - } -} diff --git a/unirest/src/test/java/BehaviorTests/CallbackFutureTest.java b/unirest/src/test/java/BehaviorTests/CallbackFutureTest.java deleted file mode 100644 index 7d81c0a10..000000000 --- a/unirest/src/test/java/BehaviorTests/CallbackFutureTest.java +++ /dev/null @@ -1,71 +0,0 @@ -/** - * The MIT License - * - * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF 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.Unirest; -import kong.unirest.NoopCallback; -import org.junit.Ignore; -import org.junit.Test; - -import static kong.unirest.MockCallback.json; - -public class CallbackFutureTest extends BddTest { - - @Test(timeout = 5000) - public void onSuccess() throws Exception { - Unirest.get(MockServer.GET) - .queryString("Snazzy", "Shoes") - .asJsonAsync() - .thenAccept(r -> { - parse(r).assertParam("Snazzy", "Shoes"); - asyncSuccess(); - }).get(); - - assertAsync(); - } - - @Test(timeout = 5000) - public 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 = 5000) @Ignore - public void onFailure() throws Exception { - Unirest.get("http://localhost:0000") - .asJsonAsync(json(this)) - .isCompletedExceptionally(); - - assertFailed("java.net.ConnectException: Connection refused"); - } -} \ No newline at end of file diff --git a/unirest/src/test/java/BehaviorTests/CertificateTests.java b/unirest/src/test/java/BehaviorTests/CertificateTests.java deleted file mode 100644 index 994490d28..000000000 --- a/unirest/src/test/java/BehaviorTests/CertificateTests.java +++ /dev/null @@ -1,229 +0,0 @@ -/** - * The MIT License - * - * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF 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.TestUtil; -import kong.unirest.Unirest; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.config.Registry; -import org.apache.http.config.RegistryBuilder; -import org.apache.http.conn.socket.ConnectionSocketFactory; -import org.apache.http.conn.socket.PlainConnectionSocketFactory; -import org.apache.http.conn.ssl.NoopHostnameVerifier; -import org.apache.http.conn.ssl.SSLConnectionSocketFactory; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; -import org.apache.http.ssl.SSLContexts; -import org.apache.http.util.EntityUtils; -import org.junit.Assert; -import org.junit.Test; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLHandshakeException; -import javax.net.ssl.SSLPeerUnverifiedException; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.security.KeyStore; - -import static org.junit.Assert.assertEquals; - -//@Ignore // dont normally run these because they depend on badssl.com -public class CertificateTests extends BddTest { - - @Test - public void canDoClientCertificates() throws Exception { - Unirest.config().clientCertificateStore(readStore(), "badssl.com"); - - Unirest.get("https://client.badssl.com/") - .asString() - .ifFailure(r -> Assert.fail(r.getStatus() + " request failed " + r.getBody())) - .ifSuccess(r -> System.out.println(" woot "));; - } - - @Test - public void canLoadKeyStoreByPath() { - Unirest.config().clientCertificateStore("src/test/resources/certs/badssl.com-client.p12", "badssl.com"); - - Unirest.get("https://client.badssl.com/") - .asString() - .ifFailure(r -> Assert.fail(r.getStatus() + " request failed " + r.getBody())) - .ifSuccess(r -> System.out.println(" woot "));; - } - - @Test - public void rawApacheClientCert() throws Exception { - SSLContext sslContext = SSLContexts.custom() - .loadKeyMaterial(readStore(), "badssl.com".toCharArray()) // use null as second param if you don't have a separate key password - .build(); - - HttpClient httpClient = HttpClients.custom().setSSLContext(sslContext).build(); - - HttpResponse response = httpClient.execute(new HttpGet("https://client.badssl.com/")); - assertEquals(200, response.getStatusLine().getStatusCode()); - HttpEntity entity = response.getEntity(); - - System.out.println("----------------------------------------"); - System.out.println(response.getStatusLine()); - EntityUtils.consume(entity); - } - - @Test - public void rawApacheWithConnectionManager() throws Exception { - SSLContext sc = SSLContexts.custom() - .loadKeyMaterial(readStore(), "badssl.com".toCharArray()) // use null as second param if you don't have a separate key password - .build(); - - SSLConnectionSocketFactory sslSocketFactory = - new SSLConnectionSocketFactory(sc, new NoopHostnameVerifier()); - - - Registry socketFactoryRegistry = - RegistryBuilder.create() - .register("https", sslSocketFactory) - .register("http", PlainConnectionSocketFactory.INSTANCE) - .build(); - - PoolingHttpClientConnectionManager cm = - new PoolingHttpClientConnectionManager(socketFactoryRegistry); - - CloseableHttpClient httpClient = - HttpClients.custom() - .setSSLSocketFactory(sslSocketFactory) - .setConnectionManager(cm) - .build(); - - Unirest.config().httpClient(httpClient); - - Unirest.get("https://client.badssl.com/") - .asString() - .ifFailure(r -> Assert.fail(r.getStatus() + " request failed " + r.getBody())) - .ifSuccess(r -> System.out.println(" woot ")); - - } - - @Test - public void badName() { - fails("https://wrong.host.badssl.com/", - SSLPeerUnverifiedException.class, - "javax.net.ssl.SSLPeerUnverifiedException: " + - "Certificate for doesn't match any of the subject alternative names: " + - "[*.badssl.com, badssl.com]"); - disableSsl(); - canCall("https://wrong.host.badssl.com/"); - } - - @Test - public void expired() { - fails("https://expired.badssl.com/", - SSLHandshakeException.class, - "javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: " + - "PKIX path validation failed: java.security.cert.CertPathValidatorException: validity check failed"); - disableSsl(); - canCall("https://expired.badssl.com/"); - } - - @Test - public void selfSigned() { - fails("https://self-signed.badssl.com/", - SSLHandshakeException.class, - "javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: " + - "PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: " + - "unable to find valid certification path to requested target"); - disableSsl(); - canCall("https://self-signed.badssl.com/"); - } - - @Test - public void badNameAsync() { - failsAsync("https://wrong.host.badssl.com/", - SSLPeerUnverifiedException.class, - "javax.net.ssl.SSLPeerUnverifiedException: " + - "Host name 'wrong.host.badssl.com' does not match the certificate subject provided by the peer " + - "(CN=*.badssl.com, O=Lucas Garron, L=Walnut Creek, ST=California, C=US)"); - disableSsl(); - canCallAsync("https://wrong.host.badssl.com/"); - } - - @Test - public void expiredAsync() { - failsAsync("https://expired.badssl.com/", - SSLHandshakeException.class, - "javax.net.ssl.SSLHandshakeException: General SSLEngine problem"); - disableSsl(); - canCallAsync("https://expired.badssl.com/"); - } - - @Test - public void selfSignedAsync() { - failsAsync("https://self-signed.badssl.com/", - SSLHandshakeException.class, - "javax.net.ssl.SSLHandshakeException: General SSLEngine problem"); - disableSsl(); - canCallAsync("https://self-signed.badssl.com/"); - } - - private void disableSsl() { - Unirest.config().reset().verifySsl(false); - } - - private void failsAsync(String url, Class exClass, String error) { - TestUtil.assertExceptionUnwrapped(() -> Unirest.get(url).asEmptyAsync().get(), - exClass, - error); - } - - private void fails(String url, Class exClass, String error) { - TestUtil.assertExceptionUnwrapped(() -> Unirest.get(url).asEmpty(), - exClass, - error); - } - - 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) { - Assert.fail(e.getMessage()); - } - } - - private KeyStore readStore() throws Exception { - try (InputStream keyStoreStream = this.getClass().getResourceAsStream("/certs/badssl.com-client.p12")) { - KeyStore keyStore = KeyStore.getInstance("PKCS12"); - keyStore.load(keyStoreStream, "badssl.com".toCharArray()); - return keyStore; - } - } -} diff --git a/unirest/src/test/java/BehaviorTests/ErrorParsingTest.java b/unirest/src/test/java/BehaviorTests/ErrorParsingTest.java deleted file mode 100644 index 65f77cc8e..000000000 --- a/unirest/src/test/java/BehaviorTests/ErrorParsingTest.java +++ /dev/null @@ -1,90 +0,0 @@ -/** - * The MIT License - * - * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF 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.HttpResponse; -import kong.unirest.Unirest; -import org.junit.Test; - -import static junit.framework.TestCase.*; - -public class ErrorParsingTest extends BddTest { - private boolean errorCalled; - - @Test - public void parsingAnAlternativeErrorObject() { - ErrorThing e = Unirest.get(MockServer.ERROR_RESPONSE) - .asObject(RequestCapture.class) - .mapError(ErrorThing.class); - - assertErrorThing(e); - } - - @Test - public void ifNoErrorThenGetTheRegularBody() { - ErrorThing error = Unirest.get(MockServer.GET) - .asObject(RequestCapture.class) - .mapError(ErrorThing.class); - - assertNull(error); - } - - @Test - public void failsIfErrorResponseCantBeMapped() { - HttpResponse request = Unirest.get(MockServer.ERROR_RESPONSE) - .asObject(RequestCapture.class); - - NotTheError error = request.mapError(NotTheError.class); - - assertEquals(null, error); - assertEquals("{\"message\":\"boom!\"}", request.getParsingError().get().getOriginalBody()); - } - - @Test - public void mapTheErrorWithAFunction() { - 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); - } - - private void assertErrorThing(ErrorThing e) { - assertEquals("boom!", e.getMessage()); - } - - public static class NotTheError { - - @JsonProperty("merp") - public String merp; - } -} diff --git a/unirest/src/test/java/BehaviorTests/LifeCycleTest.java b/unirest/src/test/java/BehaviorTests/LifeCycleTest.java deleted file mode 100644 index 9840d5678..000000000 --- a/unirest/src/test/java/BehaviorTests/LifeCycleTest.java +++ /dev/null @@ -1,206 +0,0 @@ -/** - * The MIT License - * - * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF 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.github.paweladamski.httpclientmock.HttpClientMock; -import com.google.common.collect.Sets; -import kong.unirest.*; -import kong.unirest.apache.AsyncIdleConnectionMonitorThread; -import kong.unirest.apache.SyncIdleConnectionMonitorThread; -import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; -import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; -import org.junit.Test; - -import java.io.IOException; -import java.lang.management.ManagementFactory; -import java.lang.reflect.Field; -import java.util.Map; -import java.util.Set; -import java.util.stream.IntStream; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.Matchers.lessThan; -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; - -@RunWith(MockitoJUnitRunner.class) -public class LifeCycleTest extends BddTest { - - @Mock - private CloseableHttpClient httpc; - @Mock - private PoolingHttpClientConnectionManager clientManager; - @Mock - private SyncIdleConnectionMonitorThread connMonitor; - @Mock - private CloseableHttpAsyncClient asyncClient; - @Mock - private AsyncIdleConnectionMonitorThread asyncMonitor; - @Mock - private PoolingNHttpClientConnectionManager manager; - - @Override - public void setUp() { - super.setUp(); - clearUnirestHooks(); - } - - @Test - public void settingACustomClient() { - HttpClientMock httpClientMock = new HttpClientMock(); - httpClientMock.onGet("http://localhost/getme").doReturn(202, "Howdy Ho!"); - Unirest.config().httpClient(httpClientMock); - HttpResponse result = Unirest.get("http://localhost/getme").asString(); - assertEquals(202, result.getStatus()); - assertEquals("Howdy Ho!", result.getBody()); - } - - @Test - public void ifClientsAreAlreadyRunningCanAddShutdownHooks() throws Exception { - assertShutdownHooks(0); - - Unirest.get(MockServer.GET).asEmpty(); - Unirest.get(MockServer.GET).asEmptyAsync(); - Unirest.config().addShutdownHook(true); - Unirest.config().addShutdownHook(true); - - assertShutdownHooks(1); - } - - @Test - public void canAddShutdownHooks() throws Exception { - assertShutdownHooks(0); - - Unirest.config().addShutdownHook(true).getClient(); - Unirest.config().addShutdownHook(true).getAsyncClient(); - - assertShutdownHooks(1); - } - - @Test - public void settingClientAfterClientHasAlreadyBeenSet() { - HttpClientMock httpClientMock = new HttpClientMock(); - httpClientMock.onGet("http://localhost/getme").doReturn(202, "Howdy Ho!"); - assertEquals(200, Unirest.get(MockServer.GET).asString().getStatus()); - Unirest.config().httpClient(httpClientMock); - HttpResponse result = Unirest.get("http://localhost/getme").asString(); - assertEquals(202, result.getStatus()); - assertEquals("Howdy Ho!", result.getBody()); - } - - @Test - public void willNotShutdownInactiveAsyncClient() throws IOException { - CloseableHttpAsyncClient asyncClient = mock(CloseableHttpAsyncClient.class); - when(asyncClient.isRunning()).thenReturn(false); - - Unirest.config().asyncClient(asyncClient); - - Unirest.shutDown(); - - verify(asyncClient, never()).close(); - } - - @Test - public void canDetectIfSystemIsRunning() { - Unirest.get(MockServer.GET).asEmpty(); - assertTrue(Unirest.isRunning()); - - Unirest.shutDown(); - assertFalse(Unirest.isRunning()); - - Unirest.get(MockServer.GET).asEmpty(); - assertTrue(Unirest.isRunning()); - } - - @Test - public void willReinitIfLibraryIsUsedAfterShutdown() { - Unirest.shutDown(); - assertFalse(Unirest.isRunning()); - - Unirest.get(MockServer.GET).asEmpty(); - assertTrue(Unirest.isRunning()); - } - - @Test - public void canGetTheCommonInstanceOfUnirest(){ - assertSame(Unirest.primaryInstance(), Unirest.primaryInstance()); - assertNotSame(Unirest.primaryInstance(), Unirest.spawnInstance()); - assertNotSame(Unirest.spawnInstance(), Unirest.spawnInstance()); - } - - @Test - public void shouldReuseThreadPool() { - int startingCount = ManagementFactory.getThreadMXBean().getThreadCount(); - IntStream.range(0,100).forEach(i -> { - Unirest.config().reset().getClient(); - Unirest.config().getAsyncClient(); - }); - assertThat(ManagementFactory.getThreadMXBean().getThreadCount(), is(lessThan(startingCount + 10))); - } - - @Test - public void testUnirestInstanceIsShutdownWhenClosed() { - UnirestInstance reference; - try (UnirestInstance instance = new UnirestInstance(new Config().setDefaultHeader("foo", "bar"))) { - reference = instance; - assertEquals(1, reference.config().getDefaultHeaders().size()); - assertEquals("bar", reference.config().getDefaultHeaders().get("foo").get(0)); - } - assertEquals(0, reference.config().getDefaultHeaders().size()); - } - - private void assertShutdownHooks(int expected) { - Set threads = getShutdownHookMap(); - - assertEquals(expected, threads.stream().filter(t -> "Unirest Apache Client Shutdown Hook".equals(t.getName())).count()); - assertEquals(expected, threads.stream().filter(t -> "Unirest Apache Async Client Shutdown Hook".equals(t.getName())).count()); - } - - private void clearUnirestHooks() { - getShutdownHookMap() - .stream() - .filter(t -> t.getName().contains("Unirest")) - .forEach(t -> Runtime.getRuntime().removeShutdownHook(t)); - } - - private Set getShutdownHookMap() { - try { - // oh this is so dirty and horrible. Set to @Ignore if it starts to be a problem. - Class clazz = Class.forName("java.lang.ApplicationShutdownHooks"); - Field field = clazz.getDeclaredField("hooks"); - field.setAccessible(true); - Set threads = ((Map) field.get(null)).keySet(); - return Sets.newHashSet(threads); - }catch (Exception e){ - throw new RuntimeException(e); - } - } -} diff --git a/unirest/src/test/java/BehaviorTests/MockServer.java b/unirest/src/test/java/BehaviorTests/MockServer.java deleted file mode 100644 index 167296512..000000000 --- a/unirest/src/test/java/BehaviorTests/MockServer.java +++ /dev/null @@ -1,256 +0,0 @@ -/** - * The MIT License - * - * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF 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.Headers; -import spark.Request; -import spark.Response; -import spark.Spark; -import spark.utils.IOUtils; -import kong.unirest.JacksonObjectMapper; -import kong.unirest.TestUtil; - -import javax.servlet.ServletOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStream; -import java.util.*; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static spark.Spark.*; - -public class MockServer { - private static int pages = 1; - private static int onPage = 1; - private static final List> responseHeaders = new ArrayList<>(); - private static final List> cookies = new ArrayList<>(); - - private static final JacksonObjectMapper om = new JacksonObjectMapper(); - private static Object responseBody; - public static final int PORT = 4567; - public static final String HOST = "http://localhost:" + PORT; - public static final String WINDOWS_LATIN_1_FILE = HOST + "data/cp1250.txt"; - public static final String REDIRECT = HOST + "/redirect"; - public static final String SPARKLE = 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 PROXY = "localhost:4567"; - 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 DELETE = HOST + "/delete"; - public static final String GZIP = HOST + "/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 void setJsonAsResponse(Object o){ - responseBody = om.writeValue(o); - } - - public static void reset(){ - responseBody = null; - responseHeaders.clear(); - cookies.clear(); - pages = 1; - onPage = 1; - } - - static { - port(PORT); - Spark.staticFileLocation("data"); - Spark.notFound(MockServer::notFound); - delete("/delete", MockServer::jsonResponse); - get("/sparkle/:spark/yippy", MockServer::sparkle); - post("/post", MockServer::jsonResponse); - get("/get", MockServer::jsonResponse); - get("/gzip", MockServer::gzipResponse); - get("/redirect", MockServer::redirect); - patch("/patch", MockServer::jsonResponse); - get("/invalid", MockServer::inValid); - options("/get", MockServer::jsonResponse); - get("/nobody", MockServer::nobody); - head("/get", MockServer::jsonResponse); - put("/post", MockServer::jsonResponse); - get("/get/:params/passed", MockServer::jsonResponse); - get("/get/:params/passed/:another", MockServer::jsonResponse); - get("/proxy", MockServer::proxiedResponse); - get("/binary", MockServer::file); - get("/paged", MockServer::paged); - post("/raw", MockServer::echo); - get("/error", MockServer::error); - Runtime.getRuntime().addShutdownHook(new Thread(Spark::stop)); - try { - new CountDownLatch(1).await(2, TimeUnit.SECONDS); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - - private static Object sparkle(Request request, Response response) { - 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.pathInfo()); - sparks.put("port()", String.valueOf(request.port())); - sparks.put("protocol()", request.protocol()); - sparks.put("requestMethod()", request.requestMethod()); - sparks.put("scheme()", request.scheme()); - sparks.put("servletPath()", request.servletPath()); - sparks.put("requestMethod()", request.requestMethod()); - sparks.put("splat()", Stream.of(request.splat()).collect(Collectors.joining(" | "))); - sparks.put("uri()", request.uri()); - sparks.put("url()", request.url()); - sparks.put("userAgent()", request.userAgent()); - sparks.put("queryString()", request.queryString()); - - return om.writeValue(sparks); - } - - private static Object error(Request request, Response response) { - return Spark.halt(400, om.writeValue(new ErrorThing("boom!"))); - } - - private static Object echo(Request request, Response response) { - return request.body(); - } - - private static Object paged(Request request, Response response) { - if(pages > onPage){ - onPage++; - response.header("nextPage", PAGED + "?page=" + onPage); - } - return jsonResponse(request, response); - } - - private static Object notFound(Request req, Response res) { - RequestCapture value = getRequestCapture(req, res); - value.setStatus(404); - return om.writeValue(value); - } - - private static Object file(Request request, Response response) throws Exception { - File f = TestUtil.rezFile("/image.jpg"); - response.raw().setContentType("application/octet-stream"); - response.raw().setHeader("Content-Disposition", "attachment;filename=image.jpg"); - response.status(200); - final ServletOutputStream out = response.raw().getOutputStream(); - final FileInputStream in = new FileInputStream(f); - IOUtils.copy(in, out); - out.close(); - in.close(); - return null; - } - - private static Object nobody(Request request, Response response) { - Spark.halt(200); - return null; - } - - private static Object redirect(Request request, Response response) { - response.redirect("/get", 301); - return null; - } - - private static Object inValid(Request request, Response response) { - response.status(400); - return "You did something bad"; - } - - private static Object gzipResponse(Request request, Response response) { - response.header("Content-Encoding", "gzip"); - return jsonResponse(request, response); - } - - private static Object proxiedResponse(Request req, Response res) { - return simpleResponse(req, res) - .orElseGet(() -> { - RequestCapture value = getRequestCapture(req, res); - value.setIsProxied(true); - return om.writeValue(value); - }); - } - - private static Optional simpleResponse(Request req, Response res) { - cookies.forEach(c -> res.cookie(c.key, c.value)); - responseHeaders.forEach(h -> res.header(h.key, h.value)); - - if(responseBody != null) { - return Optional.of(responseBody); - } - return Optional.empty(); - } - - private static Object jsonResponse(Request req, Response res) { - return simpleResponse(req, res) - .orElseGet(() -> { - RequestCapture value = getRequestCapture(req, res); - return om.writeValue(value); - }); - } - - private static RequestCapture getRequestCapture(Request req, Response res) { - RequestCapture value = new RequestCapture(req); - value.writeBody(req); - return value; - } - - public static void shutdown() { - Spark.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 main(String[] args){ - - } - - public static void expectCookie(String name, String value) { - cookies.add(new Pair<>(name, value)); - } -} diff --git a/unirest/src/test/java/BehaviorTests/PagingTest.java b/unirest/src/test/java/BehaviorTests/PagingTest.java deleted file mode 100644 index df4887c9e..000000000 --- a/unirest/src/test/java/BehaviorTests/PagingTest.java +++ /dev/null @@ -1,73 +0,0 @@ -/** - * The MIT License - * - * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF 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.Test; -import kong.unirest.PagedList; -import kong.unirest.Unirest; - -import static org.junit.Assert.assertEquals; - -public class PagingTest extends BddTest { - - @Test - public void canFollowPaging() { - MockServer.expectedPages(10); - - PagedList result = Unirest.get(MockServer.PAGED) - .asPaged( - r -> r.asObject(RequestCapture.class), - r -> r.getHeaders().getFirst("nextPage") - ); - - assertEquals(10, result.size()); - } - - @Test - public void canCapturePagesAsStrings() { - MockServer.expectedPages(10); - - PagedList result = Unirest.get(MockServer.PAGED) - .asPaged( - r -> r.asString(), - r -> r.getHeaders().getFirst("nextPage") - ); - - assertEquals(10, result.size()); - } - - @Test - public void willReturnOnePageIfthereWasNoPaging() { - - PagedList result = Unirest.get(MockServer.PAGED) - .asPaged( - r -> r.asObject(RequestCapture.class), - r -> null - ); - - assertEquals(1, result.size()); - } -} diff --git a/unirest/src/test/java/BehaviorTests/PathParamTest.java b/unirest/src/test/java/BehaviorTests/PathParamTest.java deleted file mode 100644 index 4715573bb..000000000 --- a/unirest/src/test/java/BehaviorTests/PathParamTest.java +++ /dev/null @@ -1,125 +0,0 @@ -/** - * The MIT License - * - * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF 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.Unirest; -import kong.unirest.UnirestException; -import org.junit.Test; -import kong.unirest.TestUtil; - -public class PathParamTest extends BddTest { - - @Test - public void canAddRouteParamsAsMap() { - String 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 - public void properlyDealsWithPlusInPAth() { - String 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 - public void testPathParameters() { - Unirest.get(MockServer.HOST + "/{method}") - .routeParam("method", "get") - .queryString("name", "Mark") - .asObject(RequestCapture.class) - .getBody() - .assertParam("name", "Mark"); - } - - @Test - public 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 - public void testPathParameters2() { - Unirest.patch(MockServer.HOST + "/{method}") - .routeParam("method", "patch") - .field("name", "Mark") - .asObject(RequestCapture.class) - .getBody() - .assertParam("name", "Mark"); - } - - @Test - public void testMissingPathParameter() { - TestUtil.assertException(() -> - Unirest.get(MockServer.HOST + "/{method}") - .routeParam("method222", "get") - .queryString("name", "Mark") - .asEmpty(), - UnirestException.class, - "Can't find route parameter name \"method222\""); - } - - @Test - public void testMissingPathParameterValue() { - TestUtil.assertException(() -> - Unirest.get(MockServer.HOST + "/{method}") - .queryString("name", "Mark") - .asEmpty(), - UnirestException.class, - "java.lang.IllegalArgumentException: Illegal character in path at index 22: http://localhost:4567/{method}?name=Mark"); - } - - @Test - public void illigalPathParams() { - String 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); - } -} diff --git a/unirest/src/test/java/BehaviorTests/ProxyTest.java b/unirest/src/test/java/BehaviorTests/ProxyTest.java deleted file mode 100644 index f2a050957..000000000 --- a/unirest/src/test/java/BehaviorTests/ProxyTest.java +++ /dev/null @@ -1,128 +0,0 @@ -/** - * The MIT License - * - * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF 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.Proxy; -import org.junit.After; -import org.junit.Ignore; -import org.junit.Test; -import kong.unirest.HttpResponse; -import kong.unirest.Unirest; - -import static org.junit.Assert.assertTrue; - -@Ignore // The Janky Proxy is pretty janky and isn't entirely stable in CI -public class ProxyTest extends BddTest { - - @After @Override - public void tearDown() { - super.tearDown(); - Unirest.shutDown(true); - JankyProxy.shutdown(); - } - - @Test - public 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 - public 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 - public void canPassProxyOnRequest() { - JankyProxy.runServer("localhost", 4567, 7777); - - Unirest.get(MockServer.GET) - .proxy("localhost", 7777) - .asObject(RequestCapture.class) - .getBody() - .assertStatus(200); - - assertTrue(JankyProxy.wasUsed()); - } - - @Test - public 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 - @Ignore // there is some weird conflict between jetty and unirest here - public 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 @Ignore // https://free-proxy-list.net/ - public 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/src/test/java/BehaviorTests/UniBodyPostingTest.java b/unirest/src/test/java/BehaviorTests/UniBodyPostingTest.java deleted file mode 100644 index 91f3a9052..000000000 --- a/unirest/src/test/java/BehaviorTests/UniBodyPostingTest.java +++ /dev/null @@ -1,169 +0,0 @@ -/** - * The MIT License - * - * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF 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.JsonNode; -import kong.unirest.MockCallback; -import kong.unirest.Unirest; -import kong.unirest.UnirestConfigException; -import org.json.JSONArray; -import org.json.JSONObject; -import org.junit.Test; - -import java.nio.charset.StandardCharsets; - -import static kong.unirest.TestUtil.assertException; - -public class UniBodyPostingTest extends BddTest { - @Test - public void testDefaults_String(){ - Unirest.post(MockServer.POST) - .body("foo") - .asObject(RequestCapture.class) - .getBody() - .assertContentType("text/plain; charset=UTF-8"); - } - - @Test - public void canSetCharsetOfBody(){ - Unirest.post(MockServer.POST) - .charset(StandardCharsets.US_ASCII) - .body("foo") - .asObject(RequestCapture.class) - .getBody() - .assertContentType("text/plain; charset=US-ASCII") - .asserBody("foo") - .assertCharset(StandardCharsets.US_ASCII); - } - - @Test - public void canSetCharsetOfBodyAfterMovingToBody(){ - Unirest.post(MockServer.POST) - .body("foo") - .charset(StandardCharsets.US_ASCII) - .asObject(RequestCapture.class) - .getBody() - .assertContentType("text/plain; charset=US-ASCII") - .asserBody("foo") - .assertCharset(StandardCharsets.US_ASCII); - } - - @Test - public void testPostRawBody() { - String sourceString = "'\"@こんにちは-test-123-" + Math.random(); - byte[] sentBytes = sourceString.getBytes(); - - Unirest.post(MockServer.POST) - .body(sentBytes) - .asObject(RequestCapture.class) - .getBody() - .asserBody(sourceString); - } - - @Test - public 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) - .asserBody("{\"hello\":\"world\"}") - .assertHeader("Content-Type", "application/json")) - ); - - assertAsync(); - } - - - @Test - public void postAPojoObjectUsingTheMapper() { - GetResponse 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() - .asserBody("{\"url\":\"http://localhost:4567/post\"}"); - } - - @Test - public void testDeleteBody() { - String body = "{\"jsonString\":{\"members\":\"members1\"}}"; - Unirest.delete(MockServer.DELETE) - .body(body) - .asObject(RequestCapture.class) - .getBody() - .asserBody(body); - } - - @Test - public void postBodyAsJson() { - JSONObject body = new JSONObject(); - body.put("krusty","krab"); - - Unirest.post(MockServer.POST) - .body(body) - .asObject(RequestCapture.class) - .getBody() - .asserBody("{\"krusty\":\"krab\"}"); - } - - @Test - public void postBodyAsJsonArray() { - JSONArray body = new JSONArray(); - body.put(0, "krusty"); - body.put(1, "krab"); - - Unirest.post(MockServer.POST) - .body(body) - .asObject(RequestCapture.class) - .getBody() - .asserBody("[\"krusty\",\"krab\"]"); - } - - @Test - public void postBodyAsJsonNode() { - JsonNode body = new JsonNode("{\"krusty\":\"krab\"}"); - - Unirest.post(MockServer.POST) - .body(body) - .asObject(RequestCapture.class) - .getBody() - .asserBody("{\"krusty\":\"krab\"}"); - } - - @Test - public void cantPostObjectWithoutObjectMapper(){ - Unirest.config().setObjectMapper(null); - - assertException(() -> Unirest.post(MockServer.POST).body(new Foo("die")), - UnirestConfigException.class, - "No Object Mapper Configured. Please config one with Unirest.config().setObjectMapper"); - } -} diff --git a/unirest/src/test/java/BehaviorTests/UploadProgressTest.java b/unirest/src/test/java/BehaviorTests/UploadProgressTest.java deleted file mode 100644 index e0fbe6a67..000000000 --- a/unirest/src/test/java/BehaviorTests/UploadProgressTest.java +++ /dev/null @@ -1,146 +0,0 @@ -/** - * The MIT License - * - * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF 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.ProgressMonitor; -import kong.unirest.Unirest; -import org.junit.Test; - -import java.io.File; -import java.io.FileInputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Stream; - -import static java.util.Arrays.asList; -import static kong.unirest.TestUtil.defaultIfNull; -import static kong.unirest.TestUtil.rezFile; -import static org.junit.Assert.assertEquals; - -public class UploadProgressTest extends BddTest { - private static class Monitor implements ProgressMonitor { - 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 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()); - } - - static class Stats { - List progress = new ArrayList<>(); - long total; - long timesCalled; - } - } - - private Monitor monitor; - private File spidey; - - @Override - public void setUp() { - super.setUp(); - this.monitor = new Monitor(); - spidey = rezFile("/spidey.jpg"); - } - - @Test - public void canAddUploadProgress() { - Unirest.post(MockServer.POST) - .field("spidey", this.spidey) - .uploadMonitor(monitor) - .asEmpty(); - - assertSpideyFileUpload("spidey.jpg"); - } - - @Test - public void canAddUploadProgressAsync() throws Exception { - Unirest.post(MockServer.POST) - .field("spidey", spidey) - .uploadMonitor(monitor) - .asEmpty(); - - assertSpideyFileUpload("spidey.jpg"); - } - - @Test - public void canKeepTrackOfMultipleFiles() { - Unirest.post(MockServer.POST) - .field("spidey", this.spidey) - .field("other", rezFile("/test")) - .uploadMonitor(monitor) - .asEmpty(); - - assertSpideyFileUpload("spidey.jpg"); - assertOtherFileUpload(); - } - - @Test - public void canMonitorIfPassedAsInputStream() throws Exception { - Unirest.post(MockServer.POST) - .field("spidey", new FileInputStream(spidey)) - .uploadMonitor(monitor) - .asEmpty(); - - assertSpideyFileUpload("spidey"); - } - - private void assertOtherFileUpload() { - Monitor.Stats stat = monitor.get("test"); - assertEquals(1, stat.timesCalled); - assertEquals(asList(19L), stat.progress); - assertEquals(19L, stat.total); - } - - private void assertSpideyFileUpload(String name) { - Monitor.Stats stat = monitor.get(name); - assertEquals(12, stat.timesCalled); - assertEquals(asList(4096L, 8192L, 12288L, 16384L, 20480L, 24576L, 28672L, - 32768L, 36864L, 40960L, 45056L, 46246L), stat.progress); - assertEquals(this.spidey.length(), stat.total); - } -} diff --git a/unirest/src/test/java/kong/unirest/BaseRequestTest.java b/unirest/src/test/java/kong/unirest/BaseRequestTest.java deleted file mode 100644 index ed7e21540..000000000 --- a/unirest/src/test/java/kong/unirest/BaseRequestTest.java +++ /dev/null @@ -1,95 +0,0 @@ -/** - * The MIT License - * - * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF 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; - -import org.junit.Test; - -import static org.junit.Assert.*; - -public class BaseRequestTest { - - @Test - public void socketTimeoutCanOverrrideConfig() { - Config config = new Config(); - config.socketTimeout(42); - - HttpRequest request = new TestRequest(config); - - assertEquals(42, request.getSocketTimeout()); - request.socketTimeout(111); - assertEquals(111, request.getSocketTimeout()); - } - - @Test - public void connectTimeoutCanOverrrideConfig() { - Config config = new Config(); - config.connectTimeout(42); - - HttpRequest request = new TestRequest(config); - - assertEquals(42, request.getConnectTimeout()); - request.connectTimeout(111); - assertEquals(111, request.getConnectTimeout()); - } - - @Test - public void copiesSettingsFromOtherRequest() { - Config config = new Config(); - config.connectTimeout(42); - config.socketTimeout(42); - - TestRequest request = new TestRequest(config); - request.socketTimeout(111).connectTimeout(222); - - HttpRequest copy = new TestRequest(request); - assertEquals(111, copy.getSocketTimeout()); - assertEquals(222, copy.getConnectTimeout()); - } - - @Test - public void canPassABasicProxyPerRequest() { - Config config = new Config(); - Proxy cp = new Proxy("foo", 8080, "username", "password"); - config.proxy(cp); - - HttpRequest request = new TestRequest(config); - - assertEquals(cp, request.getProxy()); - request.proxy("bar", 7979); - assertEquals("bar", request.getProxy().getHost()); - assertEquals(7979, request.getProxy().getPort().intValue()); - } - - private class TestRequest extends BaseRequest { - TestRequest(BaseRequest httpRequest) { - super(httpRequest); - } - - TestRequest(Config config) { - super(config, HttpMethod.GET, ""); - } - } -} \ No newline at end of file diff --git a/unirest/src/test/java/kong/unirest/ClientFactoryTest.java b/unirest/src/test/java/kong/unirest/ClientFactoryTest.java deleted file mode 100644 index 07825e7d0..000000000 --- a/unirest/src/test/java/kong/unirest/ClientFactoryTest.java +++ /dev/null @@ -1,71 +0,0 @@ -/** - * The MIT License - * - * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF 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; - -import org.apache.http.HttpRequestInterceptor; -import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.lang.management.ManagementFactory; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.Matchers.lessThan; -import static org.junit.Assert.*; -import static org.mockito.Mockito.mock; - -public class ClientFactoryTest { - - @Before @After - public void before(){ - Unirest.shutDown(true); - } - - @Test - public void shouldReuseThreadPool() { - int startingCount = ManagementFactory.getThreadMXBean().getThreadCount(); - //IntStream.range(0,100).forEach(i -> ClientFactory.refresh()); - assertThat(ManagementFactory.getThreadMXBean().getThreadCount(), is(lessThan(startingCount + 10))); - } - - @Test - public void canSaveSomeOptions(){ - HttpRequestInterceptor i = mock(HttpRequestInterceptor.class); - CloseableHttpAsyncClient c = mock(CloseableHttpAsyncClient.class); - - Unirest.config() - .addInterceptor(i) - .connectTimeout(4000) - .asyncClient(c); - - Unirest.shutDown(false); - - assertNotEquals(c, Unirest.config().getAsyncClient()); - assertEquals(i, Unirest.config().getInterceptors().get(0)); - assertEquals(4000, Unirest.config().getConnectionTimeout()); - } -} \ No newline at end of file diff --git a/unirest/src/test/java/kong/unirest/ConfigTest.java b/unirest/src/test/java/kong/unirest/ConfigTest.java deleted file mode 100644 index 7389a5c74..000000000 --- a/unirest/src/test/java/kong/unirest/ConfigTest.java +++ /dev/null @@ -1,268 +0,0 @@ -/** - * The MIT License - * - * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF 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; - -import kong.unirest.apache.ApacheAsyncClient; -import kong.unirest.apache.ApacheClient; -import kong.unirest.apache.AsyncIdleConnectionMonitorThread; -import kong.unirest.apache.SyncIdleConnectionMonitorThread; -import org.apache.http.client.HttpClient; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; -import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; -import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager; -import org.apache.http.nio.client.HttpAsyncClient; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; - -import java.io.IOException; - -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; -import static org.mockito.Mockito.doThrow; - -@RunWith(MockitoJUnitRunner.class) -public class ConfigTest { - - @Mock - private CloseableHttpClient httpc; - @Mock - private PoolingHttpClientConnectionManager clientManager; - @Mock - private SyncIdleConnectionMonitorThread connMonitor; - @Mock - private CloseableHttpAsyncClient asyncClient; - @Mock - private AsyncIdleConnectionMonitorThread asyncMonitor; - @Mock - private PoolingNHttpClientConnectionManager manager; - - @InjectMocks - private Config config; - - @Test - public void shouldKeepConnectionTimeOutDefault(){ - assertEquals(Config.DEFAULT_CONNECT_TIMEOUT, config.getConnectionTimeout()); - } - - @Test - public void shouldKeepSocketTimeoutDefault(){ - assertEquals(Config.DEFAULT_SOCKET_TIMEOUT, config.getSocketTimeout()); - } - - @Test - public void shouldKeepMaxTotalDefault(){ - assertEquals(Config.DEFAULT_MAX_CONNECTIONS, config.getMaxConnections()); - } - - @Test - public void shouldKeepMaxPerRouteDefault(){ - assertEquals(Config.DEFAULT_MAX_PER_ROUTE, config.getMaxPerRoutes()); - } - - @Test - public void onceTheConfigIsRunningYouCannotChangeConfig(){ - config.httpClient(mock(HttpClient.class)); - config.asyncClient(mock(HttpAsyncClient.class)); - - TestUtil.assertException(() -> config.socketTimeout(533), - UnirestConfigException.class, - "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."); - - Unirest.shutDown(); - } - - @Test - public void willNotRebuildIfNotClosableAsyncClient() { - HttpAsyncClient c = mock(HttpAsyncClient.class); - config.asyncClient(c); - - assertSame(c, config.getAsyncClient().getClient()); - assertSame(c, config.getAsyncClient().getClient()); - } - - @Test - public void willRebuildIfEmpty() { - assertSame(config.getAsyncClient(), config.getAsyncClient()); - } - - @Test - public void willRebuildIfClosableAndStopped() { - CloseableHttpAsyncClient c = mock(CloseableHttpAsyncClient.class); - when(c.isRunning()).thenReturn(false); - - config.asyncClient(c); - - assertNotSame(c, config.getAsyncClient()); - } - - @Test - public void testShutdown() throws IOException { - when(asyncClient.isRunning()).thenReturn(true); - - Unirest.config() - .httpClient(new ApacheClient(httpc, null, clientManager, connMonitor)) - .asyncClient(new ApacheAsyncClient(asyncClient, null, manager, asyncMonitor)); - - Unirest.shutDown(); - - verify(httpc).close(); - verify(clientManager).close(); - verify(connMonitor).interrupt(); - verify(asyncClient).close(); - verify(asyncMonitor).interrupt(); - } - - @Test - public void willPowerThroughErrors() throws IOException { - when(asyncClient.isRunning()).thenReturn(true); - doThrow(new IOException("1")).when(httpc).close(); - doThrow(new RuntimeException("2")).when(clientManager).close(); - doThrow(new RuntimeException("3")).when(connMonitor).interrupt(); - doThrow(new IOException("4")).when(asyncClient).close(); - doThrow(new RuntimeException("5")).when(asyncMonitor).interrupt(); - - Unirest.config() - .httpClient(new ApacheClient(httpc, null, clientManager, connMonitor)) - .asyncClient(new ApacheAsyncClient(asyncClient,null, manager, asyncMonitor)); - - - TestUtil.assertException(Unirest::shutDown, - UnirestException.class, - "java.io.IOException 1\n" + - "java.lang.RuntimeException 2\n" + - "java.lang.RuntimeException 3\n" + - "java.io.IOException 4\n" + - "java.lang.RuntimeException 5"); - - verify(httpc).close(); - verify(clientManager).close(); - verify(connMonitor).interrupt(); - verify(asyncClient).close(); - verify(asyncMonitor).interrupt(); - } - - @Test - public void doesNotBombOnNullOptions() throws IOException { - when(asyncClient.isRunning()).thenReturn(true); - - Unirest.config() - .httpClient(new ApacheClient(httpc, null, null, null)) - .asyncClient(new ApacheAsyncClient(asyncClient, null, null, null)); - - Unirest.shutDown(); - - verify(httpc).close(); - verify(asyncClient).close(); - } - - @Test - public void ifTheNextAsyncClientThatIsReturnedIsAlsoOffThrowAnException(){ - AsyncClient c = mock(AsyncClient.class); - when(c.isRunning()).thenReturn(false); - config.asyncClient(g -> c); - - - TestUtil.assertException(() -> config.getAsyncClient(), - UnirestConfigException.class, - "Attempted to get a new async client but it was not started. Please ensure it is"); - } - - @Test - public void willNotRebuildIfRunning() { - CloseableHttpAsyncClient c = mock(CloseableHttpAsyncClient.class); - when(c.isRunning()).thenReturn(true); - - config.asyncClient(c); - - assertSame(c, config.getAsyncClient().getClient()); - } - - @Test - public void provideYourOwnClientBuilder() { - Client cli = mock(Client.class); - - config.httpClient(c -> cli); - - assertSame(cli, config.getClient()); - } - - @Test - public void canSignalForShutdownHook() { - assertFalse(config.shouldAddShutdownHook()); - config.addShutdownHook(true); - assertTrue(config.shouldAddShutdownHook()); - } - - @Test - public void canDisableGZipencoding() { - assertTrue(config.isRequestCompressionOn()); - config.requestCompression(false); - assertFalse(config.isRequestCompressionOn()); - - } - - @Test - public void canDisableAuthRetry() { - assertTrue(config.isAutomaticRetries()); - config.automaticRetries(false); - assertFalse(config.isAutomaticRetries()); - } - - @Test - public void provideYourOwnAsyncClientBuilder() { - AsyncClient cli = mock(AsyncClient.class); - when(cli.isRunning()).thenReturn(true); - - config.asyncClient(c -> cli); - - assertSame(cli, config.getAsyncClient()); - } - - @Test - public 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"); - } - - 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/TestUtil.java b/unirest/src/test/java/kong/unirest/TestUtil.java deleted file mode 100644 index 773232ba1..000000000 --- a/unirest/src/test/java/kong/unirest/TestUtil.java +++ /dev/null @@ -1,200 +0,0 @@ -/** - * The MIT License - * - * Copyright for portions of unirest-java are held by Kong Inc (c) 2013. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF 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; - -import BehaviorTests.MockServer; -import BehaviorTests.UploadProgressTest; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.datatype.guava.GuavaModule; -import com.google.common.base.Charsets; -import com.google.common.io.Resources; - -import javax.net.ssl.SSLPeerUnverifiedException; -import java.io.*; -import java.net.URISyntaxException; -import java.util.Base64; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Supplier; -import java.util.stream.Collectors; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.fail; - -public class TestUtil { - private static final ObjectMapper om = new ObjectMapper(); - - static { - om.registerModule(new GuavaModule()); - } - - public static String toString(InputStream is) { - return new BufferedReader(new InputStreamReader(is)) - .lines().collect(Collectors.joining("\n")); - } - - public static T readValue(InputStream i, Class clss) { - try { - return om.readValue(i, clss); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - public static void assertException(ExRunnable runnable, Class exClass, String message) { - try{ - runnable.run(); - fail("Expected exception but got none. \nExpected " + exClass); - } catch (Exception e){ - if (!e.getClass().isAssignableFrom(exClass)) { - fail("Expected wrong exception type \n Expected: " + exClass + "\n but got " + e.getClass()); - } - assertEquals("Wrong Error Message", message, e.getMessage()); - } - } - - public static void assertExceptionUnwrapped(ExRunnable runnable, Class exClass, String message) { - try{ - runnable.run(); - fail("Expected exception but got none. \nExpected " + exClass); - } catch (Exception e){ - if (!e.getCause().getClass().isAssignableFrom(exClass)) { - fail("Expected wrong exception type \n Expected: " + exClass + "\n but got " + e.getCause().getClass()); - } - assertEquals("Wrong Error Message", message, e.getMessage()); - } - } - - public static T readValue(String body, Class as) { - try { - return om.readValue(body, as); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - public static InputStream emptyInput() { - return new ByteArrayInputStream(new byte[]{}); - } - - public static InputStream toInputStream(String s) { - return new ByteArrayInputStream(s.getBytes()); - } - - 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 void debugApache() { - System.setProperty("org.apache.commons.logging.Log","org.apache.commons.logging.impl.SimpleLog"); - System.setProperty("org.apache.commons.logging.simplelog.showdatetime", "true"); - System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.http", "DEBUG"); - System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.http.wire", "DEBUG"); - } - - public static T read(String o, Class as){ - try { - return om.readValue(o, as); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - public static void assertBasicAuth(String raw, String username, String password) { - assertNotNull("Authorization Header Missing", raw); - String credentials = raw.replace("Basic ",""); - assertEquals(username + ":" + password, new String(Base64.getDecoder().decode(credentials))); - } - - 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 File rezFile(String name) { - try { - return new File(MockServer.class.getResource(name).toURI()); - } catch (URISyntaxException 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 File getImageFile() { - return rezFile("/image.jpg"); - } - - public static T defaultIfNull(T t, Supplier supplier) { - if(t == null){ - return supplier.get(); - } - return t; - } - - @FunctionalInterface - public interface ExRunnable { - /** - * When an object implementing interface Runnable is used - * to create a thread, starting the thread causes the object's - * run method to be called in that separately executing - * thread. - *

- * The general contract of the method run is that it may - * take any action whatsoever. - * - * @see java.lang.Thread#run() - */ - public abstract void run() throws Exception; - } - -} 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..663554fc3 --- /dev/null +++ b/unirest/src/test/java/kong/unirest/core/BodyTest.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 kong.unirest.core.Body; +import kong.unirest.core.BodyPart; +import kong.unirest.core.ParamPart; +import kong.unirest.core.TestBody; +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/HeadersTest.java b/unirest/src/test/java/kong/unirest/core/ByteArrayPartTest.java similarity index 69% rename from unirest/src/test/java/kong/unirest/HeadersTest.java rename to unirest/src/test/java/kong/unirest/core/ByteArrayPartTest.java index 78b7329c4..0d00235e5 100644 --- a/unirest/src/test/java/kong/unirest/HeadersTest.java +++ b/unirest/src/test/java/kong/unirest/core/ByteArrayPartTest.java @@ -23,30 +23,27 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; -import org.junit.Test; +import kong.unirest.core.ByteArrayPart; +import kong.unirest.core.ContentType; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.*; -public class HeadersTest { +class ByteArrayPartTest { + ByteArrayPart part = new ByteArrayPart( + "uploadfile", + "hey".getBytes(), ContentType.APPLICATION_ATOM_XML, + "foo.xml"); @Test - public void canGetApacheHeaders() { - Headers headers = new Headers(); - headers.add("foo","bar"); - - Header h = headers.all().get(0); - - assertEquals("foo", h.getName()); - assertEquals("bar", h.getValue()); + void byteArraysAreFiles() { + assertTrue(part.isFile()); } @Test - public void dontBombOnNull(){ - Headers h = new Headers(); - h.add(null, "foo"); - - assertEquals(0, h.size()); + 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/ContentTypeTest.java b/unirest/src/test/java/kong/unirest/core/ContentTypeTest.java similarity index 56% rename from unirest/src/test/java/kong/unirest/ContentTypeTest.java rename to unirest/src/test/java/kong/unirest/core/ContentTypeTest.java index 690e42dcd..a99339001 100644 --- a/unirest/src/test/java/kong/unirest/ContentTypeTest.java +++ b/unirest/src/test/java/kong/unirest/core/ContentTypeTest.java @@ -23,38 +23,32 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; - -public class ContentTypeTest { +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +class ContentTypeTest { @Test - public void contentTypeWithEncoding() { - verifySame(org.apache.http.entity.ContentType.APPLICATION_ATOM_XML, - ContentType.APPLICATION_ATOM_XML); + void checkForBinaryTypes() { + assertTrue(ContentType.BINARY_OCTET_STREAM.isBinary()); + assertTrue(ContentType.APPLICATION_OCTET_STREAM.isBinary()); + assertFalse(ContentType.APPLICATION_JSON.isBinary()); } @Test - public void imageTypes() { - verifySame(org.apache.http.entity.ContentType.IMAGE_GIF, - ContentType.IMAGE_GIF); + 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 - public void wildCard() { - verifySame(org.apache.http.entity.ContentType.WILDCARD, - ContentType.WILDCARD); - } - - private void verifySame(org.apache.http.entity.ContentType apache, ContentType unirest) { - assertEquals( - apache.toString(), - unirest.toString() - ); - assertEquals(apache.toString(), - org.apache.http.entity.ContentType.parse(unirest.toString()).toString()); + 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..bbdb8b4a1 --- /dev/null +++ b/unirest/src/test/java/kong/unirest/core/HeadersTest.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.core; + + +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static java.util.Arrays.asList; +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()); + } +} \ 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/NotImplimented.java b/unirest/src/test/java/kong/unirest/core/NotImplemented.java similarity index 93% rename from unirest/src/test/java/kong/unirest/NotImplimented.java rename to unirest/src/test/java/kong/unirest/core/NotImplemented.java index a6914f2c4..0c2c03008 100644 --- a/unirest/src/test/java/kong/unirest/NotImplimented.java +++ b/unirest/src/test/java/kong/unirest/core/NotImplemented.java @@ -23,7 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; -public class NotImplimented extends RuntimeException { +public class NotImplemented extends RuntimeException { } diff --git a/unirest/src/test/java/kong/unirest/PagedListTest.java b/unirest/src/test/java/kong/unirest/core/PagedListTest.java similarity index 86% rename from unirest/src/test/java/kong/unirest/PagedListTest.java rename to unirest/src/test/java/kong/unirest/core/PagedListTest.java index 0df6aa0a5..c79bc9c01 100644 --- a/unirest/src/test/java/kong/unirest/PagedListTest.java +++ b/unirest/src/test/java/kong/unirest/core/PagedListTest.java @@ -23,24 +23,27 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; import com.google.common.base.Strings; -import org.junit.Test; +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.Assert.assertEquals; +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; -public class PagedListTest { +class PagedListTest { @Test - public void canGetAllTheBodies() { + void canGetAllTheBodies() { PagedList list = new PagedList<>(); list.addAll(asList( mkRequest("foo"), @@ -53,7 +56,7 @@ public void canGetAllTheBodies() { } @Test - public void bodiesMustBeSucessful() { + void bodiesMustBeSuccessful() { PagedList list = new PagedList<>(); list.addAll(asList( mkRequest("foo"), @@ -66,7 +69,7 @@ public void bodiesMustBeSucessful() { } @Test - public void canProcessSuccessfullResuls() { + void canProcessSuccessfulResults() { PagedList list = new PagedList<>(); list.addAll(asList( mkRequest("foo"), @@ -80,7 +83,7 @@ public void canProcessSuccessfullResuls() { } @Test - public void canProcessFailed() { + void canProcessFailed() { PagedList list = new PagedList<>(); list.addAll(asList( mkRequest("foo"), @@ -90,9 +93,10 @@ public void canProcessFailed() { final List processed = new ArrayList<>(); list.ifFailure(e -> processed.add(e.getBody())); - assertEquals(null, processed.get(0)); + assertNull(processed.get(0)); } + @SuppressWarnings("unchecked") private HttpResponse mkRequest(String foo) { HttpResponse r = mock(HttpResponse.class); when(r.getBody()).thenReturn(foo); 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/ResponseUtilsTest.java b/unirest/src/test/java/kong/unirest/core/ResponseUtilsTest.java similarity index 54% rename from unirest/src/test/java/kong/unirest/ResponseUtilsTest.java rename to unirest/src/test/java/kong/unirest/core/ResponseUtilsTest.java index d56266526..86fda3148 100644 --- a/unirest/src/test/java/kong/unirest/ResponseUtilsTest.java +++ b/unirest/src/test/java/kong/unirest/core/ResponseUtilsTest.java @@ -23,30 +23,26 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.jupiter.MockitoExtension; -import java.io.InputStream; -import java.io.InputStreamReader; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.when; -@RunWith(MockitoJUnitRunner.class) -public class ResponseUtilsTest { +@ExtendWith(MockitoExtension.class) +class ResponseUtilsTest { @Mock private Config config; @InjectMocks - TestResponse test; + TestRawResponse test; @Test - public void getCharsetDefaults() { + void getCharsetDefaults() { defaultEncoding("UTF-8"); assertEquals("UTF-8", getCharSet(null)); @@ -57,12 +53,12 @@ public void getCharsetDefaults() { } @Test - public void contentTypeWhenYouGotIt() { + void contentTypeWhenYouGotIt() { assertEquals("LATIN-1", getCharSet("Content-Type: text/html; charset=latin-1")); } @Test - public void changeTheDefault() { + void changeTheDefault() { defaultEncoding("KINGON-1"); assertEquals("KINGON-1", getCharSet(null)); defaultEncoding("SINDARIN-42"); @@ -78,72 +74,4 @@ private String getCharSet(String content) { return test.getCharSet(); } - public static class TestResponse extends RawResponseBase { - - public String type; - - protected TestResponse(Config config) { - super(config); - } - - @Override - public int getStatus() { - return 0; - } - - @Override - public String getStatusText() { - return null; - } - - @Override - public Headers getHeaders() { - return null; - } - - @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; - } - } } \ 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/main/java/kong/unirest/RawResponse.java b/unirest/src/test/java/kong/unirest/core/TestRawResponse.java similarity index 50% rename from unirest/src/main/java/kong/unirest/RawResponse.java rename to unirest/src/test/java/kong/unirest/core/TestRawResponse.java index 8b9fc11ab..f5315ea88 100644 --- a/unirest/src/main/java/kong/unirest/RawResponse.java +++ b/unirest/src/test/java/kong/unirest/core/TestRawResponse.java @@ -23,23 +23,76 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; import java.io.InputStream; import java.io.InputStreamReader; -public interface RawResponse { - int getStatus(); - String getStatusText(); - Headers getHeaders(); - InputStream getContent(); - byte[] getContentAsBytes(); - String getContentAsString(); - String getContentAsString(String charset); - InputStreamReader getContentReader(); - boolean hasContent(); - String getContentType(); - String getEncoding(); - Config getConfig(); - HttpResponseSummary toSummary(); +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/UriFormatterTest.java b/unirest/src/test/java/kong/unirest/core/UriFormatterTest.java similarity index 89% rename from unirest/src/test/java/kong/unirest/UriFormatterTest.java rename to unirest/src/test/java/kong/unirest/core/UriFormatterTest.java index a57ea68f0..73ac4efdd 100644 --- a/unirest/src/test/java/kong/unirest/UriFormatterTest.java +++ b/unirest/src/test/java/kong/unirest/core/UriFormatterTest.java @@ -23,13 +23,15 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package kong.unirest; +package kong.unirest.core; -import org.junit.Test; -import java.util.Optional; +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.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; public class UriFormatterTest { 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/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/certs/badssl.com-client.p12 b/unirest/src/test/resources/certs/badssl.com-client.p12 deleted file mode 100644 index f021abaa1..000000000 Binary files a/unirest/src/test/resources/certs/badssl.com-client.p12 and /dev/null differ diff --git a/unirest/src/test/resources/certs/badssl.com-client.pem b/unirest/src/test/resources/certs/badssl.com-client.pem deleted file mode 100644 index b71c24e1b..000000000 --- a/unirest/src/test/resources/certs/badssl.com-client.pem +++ /dev/null @@ -1,64 +0,0 @@ -Bag Attributes - localKeyID: 28 D8 AB 79 A7 97 6F EE D3 6D 1D F0 B9 AC 3D 3F 36 B7 C4 DF -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----- -MIIEnTCCAoWgAwIBAgIJAPC7KMFjfslXMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNV -BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNp -c2NvMQ8wDQYDVQQKDAZCYWRTU0wxMTAvBgNVBAMMKEJhZFNTTCBDbGllbnQgUm9v -dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMTcxMTE2MDUzNjMzWhcNMTkxMTE2 -MDUzNjMzWjBvMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQG -A1UEBwwNU2FuIEZyYW5jaXNjbzEPMA0GA1UECgwGQmFkU1NMMSIwIAYDVQQDDBlC -YWRTU0wgQ2xpZW50IENlcnRpZmljYXRlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A -MIIBCgKCAQEAxzdfEeseTs/rukjly6MSLHM+Rh0enA3Ai4Mj2sdl31x3SbPoen08 -utVhjPmlxIUdkiMG4+ffe7N+JtDLG75CaxZp9CxytX7kywooRBJsRnQhmQPca8MR -WAJBIz+w/L+3AFkTIqWBfyT+1VO8TVKPkEpGdLDovZOmzZAASi9/sj+j6gM7AaCi -DeZTf2ES66abA5pOp60Q6OEdwg/vCUJfarhKDpi9tj3P6qToy9Y4DiBUhOct4MG8 -w5XwmKAC+Vfm8tb7tMiUoU0yvKKOcL6YXBXxB2kPcOYxYNobXavfVBEdwSrjQ7i/ -s3o6hkGQlm9F7JPEuVgbl/Jdwa64OYIqjQIDAQABoy0wKzAJBgNVHRMEAjAAMBEG -CWCGSAGG+EIBAQQEAwIHgDALBgNVHQ8EBAMCBeAwDQYJKoZIhvcNAQELBQADggIB -AKpzk1ZTunWuof3DIer2Abq7IV3STGeFaoH4TuHdSbmXwC0KuPkv7wVPgPekyRaH -b9CBnsreRF7eleD1M63kakhdnA1XIbdJw8sfSDlKdI4emmb4fzdaaPxbrkQ5IxOB -QDw5rTUFVPPqFWw1bGP2zrKD1/i1pxUtGM0xem1jR7UZYpsSPs0JCOHKZOmk8OEW -Uy+Jp4gRzbMLZ0TrvajGEZXRepjOkXObR81xZGtvTNP2wl1zm13ffwIYdqJUrf1H -H4miU9lVX+3/Z+2mVHBWhzBgbTmo06s3uwUE6JsxUGm2/w4NNblRit0uQcGw7ba8 -kl2d5rZQscFsqNFz2vRjj1G0dO8S3owmuF0izZO9Fqvq0jB6oaUkxcAcTKFSjs2z -wy1oy+cu8iO3GRbfAW7U0xzGp9MnkdPS5dHzvhod3/DK0YVskfxZF7M8GhkjT7Qm -2EUBQNNMNXC3g/GXTdXOgqqjW5GXahI8Z6Q4OYN6xZwuEhizwKkgojwaww2YgYT9 -MJXciJZWr3QXvFdBH7m0zwpKgQ1wm6j3yeyuRphq2lEtU3OQl55A3tXtvqyMXsxk -xMCCNQdmKQt0WYmMS3Xj/AfAY2sjCWziDflvW5mGCUjSYdZ+r3JIIF4m/FNCIO1d -Ioacp9qb0qL9duFlVHtFiPgoKrEdJaNVUL7NG9ppF8pR ------END CERTIFICATE----- -Bag Attributes - localKeyID: 28 D8 AB 79 A7 97 6F EE D3 6D 1D F0 B9 AC 3D 3F 36 B7 C4 DF -Key Attributes: ------BEGIN RSA PRIVATE KEY----- -Proc-Type: 4,ENCRYPTED -DEK-Info: DES-EDE3-CBC,8906E5B87ECB8682 - -cY/EOwDILOiCPzyqZanlrhrb5h+fCnS/mHhsNme0Zyrk9udaBZKwxkPtUC6rJZ9/ -rX4HQch9tuVy992CDe1bl4l+3EfqshUhxtivG8GHiFj7TNo5Ia9LaE+KwugB2rdX -J8odwWmwE8Y9TX09WyPKHikjg2nAOgN3Gf+GeJW6fL9xcEVmIgDyX6kBusQYWIQ3 -PMYqyaBhL8UCZVRpiUINTrJXCnNAI4t6K0i4J+hjyekRnA5PQ+A/0+C9J+kQW+wj -uDWkQtyyBNsscGhXrL9ita7/UtJa+/aS99FLTnxD+fqyU4svzNIQWmZBcIxb9Lxq -LHcxNaQCtFycD1iU8Tsq9HY1apKrdPBjaaYne6qCg8nIPWpo3gWoffu+R3EfVcdZ -lNgKL3Cd1oCJy/Vcz+u0iayCPT0zfP1dLlGpL2U7L7+8pJ88FbXt/F1th9QEUp5z -dISg/6ukHyIkfXwoxYXkin9fW2clo99+fuXkJaVPEOnRmr0+kf+1S0Uw8fkete4v -f0IUxUwydxCXFN2ZFZ6o/LFLMLRfNAqGSGjqGeY1L0ILkJPo3KBqlmUhl2FIROOW -4rSrfpujjsOqlPncJ9apW04RnqhY7t0YOtp0rMK+gIDteKD+utCyh+UAetiiqTiB -ZDup/kzPNDClSDU6cgRbZUf/Nt8RiBcVeX4TfDb1eEhqV0BTHrLOBo8yp6ETDlJM -CKO5i9kb5fUaFPFD5VTc5rnY4qV/hUj/uyAVK757A+m9nn7fYOMeaRAgOEvChYSf -Qam0VPgpJxfjsaw0BFOMyfsHNJqvTbPXA+hIKD3fhP6jNcgpKTLHJp89J4XUOeIR -tvh/u7vljhVygP7ZQOpCoX1xSMdJMO7Qhrh3O/2fq/EJertJD2PQ0Zgqb8wosHn/ -Aw9VWT6849jL55Xd5l3zXmI9vU0Le4HP3NstV9jjpcp91dU9yfQpcuIo8U26u+4r -LR1VmhZJjo7FBOpyJZ1Jb3vyp+nPI+tH214DhM9LuXvMbf3ORhXTMOlqSABUmo/+ -t+QjVfcEuhFR9CTVWZUIXflJk/euvzqTQdm8iz7JuFzQOhoXjiPIq0GqtCk10SeL -zqHz1s0TZNcrZyzkmiHuWjGVwHN/XZA3dW67uj522hD7EzucKE9CoCdJ3f4JEWmS -CQwEbba6SKAR6iBlouIYLVdkOBgimGiF13rCwvdN234hQeJT8Wc87iG+uDw73PJL -+amDglATH4wIpBk4xmjh/GTRDK0yH9jp7Dv9iwShbjk0yOuoz5yDn8VPqRIREn8d -9sAaiFUUQ/9XrdlA5F+49OznClDWLKHK8sSAAFyrzvoCcqseSKbvLyrlHGT0fiot -obgDu/W+K2xEOjQeaIyVI5J1qOi6k78fyv1vutjEs6mcTRtDAIxi+V5y5lXUEj0v -OWYsbp9yb8Yq602vV8UYSROd+1xdE+7Td3ENLYE7MnVqju7a5NRfnZYgAU03NgIf -nHGFZC6/tMz/PXS+D0dqzXxwEjH5JQzGBjvSQHK09gHtCfcyshMQQWtXZGQViZX8 -QdYXiaq67nJex0DjWTt56a4EgsdYC1J28bJ3GAkrWNkDFRmlx49zvA== ------END RSA PRIVATE KEY-----