diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 91ee3e3..5cc98ad 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -11,7 +11,5 @@ jobs: uses: actions/setup-java@v1 with: java-version: '11' - - name: Grant execute permission for gradlew - run: chmod +x gradlew - name: Build with Gradle run: ./gradlew clean build \ No newline at end of file diff --git a/README.md b/README.md index 023d945..bad289d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Ok2Curl [![Commit check](https://github.com/mrmike/Ok2Curl/actions/workflows/push.yml/badge.svg)](https://github.com/mrmike/Ok2Curl/actions/workflows/push.yml) [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-Ok2Curl-green.svg?style=flat)](https://android-arsenal.com/details/1/2653) [![Release](https://jitpack.io/v/mrmike/Ok2Curl.svg)](https://jitpack.io/#mrmike/Ok2Curl) -Convert OkHttp requests into curl logs. +Simply way to transform OkHttp requests into curl logs. ## Usage Add library to project dependencies. Library is hosted on Maven Central. @@ -10,20 +10,19 @@ repositories { } dependencies { - implementation 'com.github.mrmike:ok2curl:0.7.0' + implementation 'com.github.mrmike:ok2curl:0.8.0' } ``` To start logging requests with Ok2Curl add interceptor to OkHttp client. -```java -OkHttpClient okHttp = new OkHttpClient.Builder() - .addInterceptor(new CurlInterceptor(new Loggable() { - @Override - public void log(String message) { - Log.v("Ok2Curl", message); - } - })) - .build(); +```kotlin +val client = OkHttpClient.Builder() + .addInterceptor(CurlInterceptor(object : Logger { + override fun log(message: String) { + Log.v("Ok2Curl", message) + } + })) + .build() ``` ## Result @@ -36,34 +35,67 @@ curl -X GET -H "Cache-Control:max-stale=2147483647, only-if-cached" https://api. ## Network interceptors By default Ok2Curl uses application interceptors from OkHttp which is adequate for most cases. But sometimes you may want to use network interceptor e.g. to log Cookies set via [CookieHandler](http://docs.oracle.com/javase/6/docs/api/java/net/CookieHandler.html). In such a case add interceptor the same way as below: -``` -OkHttpClient okHttp = new OkHttpClient.Builder() - .addNetworkInterceptor(new CurlInterceptor()) - .build(); +```kotlin +val client = OkHttpClient.Builder() + .addNetworkInterceptor(CurlInterceptor(logger)) + .build() ``` To get know more about Interceptor in OkHttp take a look here: https://github.com/square/okhttp/wiki/Interceptors +## Configuration + +`CurlInterceptor` accepts an optional configuration object. With `Configuration` you can specify various options like: +* header modifiers - custom logic for modifying header values +* components - list of required command components +* flags - optional curl flags +* limit - bytes limit for body +* delimiter for command components + +```kotlin +class Configuration( + val headerModifiers: List = emptyList(), + val components: List = CommandComponent.DEFAULT, + val flags: Flags = Flags.EMPTY, + val limit: Long = 1024L * 1024L, + val delimiter: String = " " +) +``` + ## Header modifiers -Ok2Curl allows you to modify any header before creating curl command. All you have to do is create your own modifier that implements [HeaderModifier](https://github.com/mrmike/Ok2Curl/blob/master/ok2curl/src/main/java/com/moczul/ok2curl/modifier/HeaderModifier.java) -and add this modifier to CurlInterceptor. See [sample](https://github.com/mrmike/Ok2Curl/blob/master/sample/src/main/java/com/moczul/sample/RequestService.java) for reference. +Ok2Curl allows you to modify any header before creating curl command. All you have to do is create your own modifier that implements [HeaderModifier](https://github.com/mrmike/Ok2Curl/blob/master/ok2curl/src/main/java/com/moczul/ok2curl/modifier/HeaderModifier.kt) +and add this modifier to CurlInterceptor. See [sample](https://github.com/mrmike/Ok2Curl/blob/master/sample/src/main/java/com/moczul/sample/RequestService.kt) for reference. +``` +val modifier = BasicAuthorizationHeaderModifier(Base64Decoder()) +val configuration = Configuration(headerModifiers = listOf(modifier)) +val curlInterceptor = CurlInterceptor(AndroidLogger(), configuration) ``` -final BasicAuthorizationHeaderModifier modifier = new BasicAuthorizationHeaderModifier(new Base64Decoder()); -final List modifiers = Collections.singletonList(modifier); -final CurlInterceptor curlInterceptor = new CurlInterceptor(new AndroidLogger(), modifiers); +## Command Components +With Ok2Curl configuration you can specify a list of components for curl command. For instance, +you can skip header, body, and flag components. +```kotlin +val components = listOf(Curl, Method, Url) +val configuration = Configuration(components = components) +val curlInterceptor = CurlInterceptor(AndroidLogger(), configuration) ``` -## Options -Ok2Curl supports basic Curl options. In order to use options use the following code: +As a result, CurlInterceptor will receive given simplified command +```shell +// Headers, body and flags are skipped +curl -X GET https://api.github.com/repos/vmg/redcarpet/issues?state=closed ``` -final Options options = Options.builder() - .insecure() - .connectTimeout(120) - .retry(5) - .build(); -final CurlInterceptor interceptor = new CurlInterceptor(logger, options); +## Flags +Ok2Curl supports basic Curl options. To use options use the following code: +```kotlin +val flags = Flags.builder() + .insecure() + .connectTimeout(seconds = 120) + .retry(5) + .build() +val configuration = Configuration(flags = flags) +val curlInterceptor = CurlInterceptor(AndroidLogger(), configuration) ``` Since now every Curl command will start with `curl --insecure --connect-timeout 120 --retry 5...` @@ -75,7 +107,7 @@ Since now every Curl command will start with `curl --insecure --connect-timeout * --compressed * --location -If would like to support any new options please feel free to open PR. Full list of curl options is +If would like to support any new options please feel free to open PR. A full list of curl options is available [here](https://curl.haxx.se/docs/manpage.html). diff --git a/build.gradle b/build.gradle index 61aed67..a982cc9 100644 --- a/build.gradle +++ b/build.gradle @@ -1,13 +1,17 @@ buildscript { + ext.kotlin_version = '1.6.20' + repositories { google() mavenCentral() maven { url "https://plugins.gradle.org/m2/" } } + dependencies { - classpath 'com.android.tools.build:gradle:7.0.4' + classpath 'com.android.tools.build:gradle:7.1.2' classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' classpath 'io.github.gradle-nexus:publish-plugin:1.1.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 87b738c..e708b1c 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0f80bbf..aa991fc 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index af6708f..4f906e0 100755 --- a/gradlew +++ b/gradlew @@ -1,5 +1,21 @@ #!/usr/bin/env sh +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + ############################################################################## ## ## Gradle start up script for UN*X @@ -28,7 +44,7 @@ APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m"' +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -66,6 +82,7 @@ esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then @@ -109,10 +126,11 @@ if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath @@ -138,19 +156,19 @@ if $cygwin ; then else eval `echo args$i`="\"$arg\"" fi - i=$((i+1)) + i=`expr $i + 1` done case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi @@ -159,14 +177,9 @@ save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } -APP_ARGS=$(save "$@") +APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 0f8d593..ac1b06f 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,3 +1,19 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @@ -13,15 +29,18 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -35,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -45,28 +64,14 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell diff --git a/ok2curl/build.gradle b/ok2curl/build.gradle index 8e6aa22..0422dd4 100644 --- a/ok2curl/build.gradle +++ b/ok2curl/build.gradle @@ -1,9 +1,9 @@ -apply plugin: 'java' +apply plugin: 'kotlin' apply plugin: 'maven-publish' ext { PUBLISH_GROUP_ID = 'com.github.mrmike' - PUBLISH_VERSION = '0.7.1' + PUBLISH_VERSION = '0.8.0' PUBLISH_ARTIFACT_ID = 'ok2curl' } @@ -12,8 +12,20 @@ apply from: "${rootProject.projectDir}/scripts/publish-module.gradle" targetCompatibility = '1.8' sourceCompatibility = '1.8' +compileKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} +compileTestKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} + dependencies { implementation 'com.squareup.okhttp3:okhttp:4.9.3' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" testImplementation 'junit:junit:4.13.2' testImplementation 'org.mockito:mockito-core:2.23.0' diff --git a/ok2curl/src/main/java/com/moczul/ok2curl/CommandComponent.kt b/ok2curl/src/main/java/com/moczul/ok2curl/CommandComponent.kt new file mode 100644 index 0000000..dadc7c8 --- /dev/null +++ b/ok2curl/src/main/java/com/moczul/ok2curl/CommandComponent.kt @@ -0,0 +1,16 @@ +package com.moczul.ok2curl + +sealed class CommandComponent { + + object Curl : CommandComponent() + object Flags : CommandComponent() + object Method : CommandComponent() + object Header : CommandComponent() + object Body : CommandComponent() + object Url : CommandComponent() + + companion object { + @JvmField + val DEFAULT = listOf(Curl, Flags, Method, Header, Body, Url) + } +} \ No newline at end of file diff --git a/ok2curl/src/main/java/com/moczul/ok2curl/ConfigurableCurlBuilder.java b/ok2curl/src/main/java/com/moczul/ok2curl/ConfigurableCurlBuilder.java deleted file mode 100644 index b1a575f..0000000 --- a/ok2curl/src/main/java/com/moczul/ok2curl/ConfigurableCurlBuilder.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.moczul.ok2curl; - -import com.moczul.ok2curl.modifier.HeaderModifier; - -import java.util.ArrayList; -import java.util.List; - -import okhttp3.Request; - -import static com.moczul.ok2curl.StringUtil.join; - -public class ConfigurableCurlBuilder extends CurlBuilder { - - public static final int PART_CURL = 0; - public static final int PART_OPTIONS = 1; - public static final int PART_METHOD = 2; - public static final int PART_HEADERS = 3; - public static final int PART_CONTENT_TYPE = 4; - public static final int PART_BODY = 5; - public static final int PART_URL = 6; - protected static final int[] DEFAULT_PARTS_ORDER = new int[]{PART_CURL, PART_OPTIONS, PART_METHOD, PART_HEADERS, PART_CONTENT_TYPE, PART_BODY, PART_URL}; - - protected final int[] partsOrder; - - public ConfigurableCurlBuilder(Request request, long limit, List headerModifiers, Options options, String delimiter) { - this(request, limit, headerModifiers, options, delimiter, DEFAULT_PARTS_ORDER); - } - - public ConfigurableCurlBuilder(Request request, long limit, List headerModifiers, Options options, String delimiter, int[] partsOrder) { - super(request, limit, headerModifiers, options, delimiter); - this.partsOrder = partsOrder; - } - - @Override - public String build() { - List parts = new ArrayList<>(); - for (int part : partsOrder) { - switch (part) { - case PART_CURL: addCurl(parts); break; - case PART_OPTIONS: addOptions(parts); break; - case PART_METHOD: addMethod(parts); break; - case PART_HEADERS: addHeaders(parts); break; - case PART_CONTENT_TYPE: addContentType(parts); break; - case PART_BODY: addBody(parts); break; - case PART_URL: addUrl(parts); break; - } - } - - return join(delimiter, parts); - } - - private void addCurl(List parts) { - parts.add("curl"); - } - - private void addOptions(List parts) { - parts.addAll(options); - } - - private void addMethod(List parts) { - parts.add(String.format(FORMAT_METHOD, method.toUpperCase())); - } - - private void addHeaders(List parts) { - for (Header header : headers) { - final String headerPart = String.format(FORMAT_HEADER, header.name(), header.value()); - parts.add(headerPart); - } - } - - private void addContentType(List parts) { - if (contentType != null && !containsName(CONTENT_TYPE, headers)) { - parts.add(String.format(FORMAT_HEADER, CONTENT_TYPE, contentType)); - } - } - - private void addBody(List parts) { - if (body != null) { - parts.add(String.format(FORMAT_BODY, body)); - } - } - - private void addUrl(List parts) { - parts.add(String.format(FORMAT_URL, url)); - } -} diff --git a/ok2curl/src/main/java/com/moczul/ok2curl/Configuration.kt b/ok2curl/src/main/java/com/moczul/ok2curl/Configuration.kt new file mode 100644 index 0000000..5f1e01a --- /dev/null +++ b/ok2curl/src/main/java/com/moczul/ok2curl/Configuration.kt @@ -0,0 +1,11 @@ +package com.moczul.ok2curl + +import com.moczul.ok2curl.modifier.HeaderModifier + +class Configuration @JvmOverloads constructor( + val headerModifiers: List = emptyList(), + val components: List = CommandComponent.DEFAULT, + val flags: Flags = Flags.EMPTY, + val limit: Long = 1024L * 1024L, + val delimiter: String = " " +) \ No newline at end of file diff --git a/ok2curl/src/main/java/com/moczul/ok2curl/CurlBuilder.java b/ok2curl/src/main/java/com/moczul/ok2curl/CurlBuilder.java deleted file mode 100644 index 9bb98cf..0000000 --- a/ok2curl/src/main/java/com/moczul/ok2curl/CurlBuilder.java +++ /dev/null @@ -1,153 +0,0 @@ -package com.moczul.ok2curl; - -import com.moczul.ok2curl.modifier.HeaderModifier; - -import java.io.IOException; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; - -import okhttp3.Headers; -import okhttp3.MediaType; -import okhttp3.Request; -import okhttp3.RequestBody; -import okio.Buffer; -import okio.BufferedSink; -import okio.Okio; - -import static com.moczul.ok2curl.StringUtil.join; - -public class CurlBuilder { - - protected static final String FORMAT_HEADER = "-H \"%1$s:%2$s\""; - protected static final String FORMAT_METHOD = "-X %1$s"; - protected static final String FORMAT_BODY = "-d '%1$s'"; - protected static final String FORMAT_URL = "\"%1$s\""; - protected static final String CONTENT_TYPE = "Content-Type"; - - protected final String url; - protected final String method; - protected final String contentType; - protected final String body; - protected final List options; - protected final List
headers; - protected final String delimiter; - - public CurlBuilder(Request request) { - this(request, -1L, Collections.emptyList(), Options.EMPTY); - } - - public CurlBuilder(Request request, long limit, List headerModifiers, Options options) { - this(request, limit, headerModifiers, options, " "); - } - - public CurlBuilder(Request request, long limit, List headerModifiers, Options options, String delimiter) { - this.url = request.url().toString(); - this.method = request.method(); - this.options = Collections.unmodifiableList(options.list()); - this.delimiter = delimiter; - final RequestBody body = request.body(); - if (body != null) { - this.contentType = getContentType(body); - this.body = getBodyAsString(body, limit); - } else { - this.contentType = null; - this.body = null; - } - - final Headers headers = request.headers(); - final List
modifiableHeaders = new LinkedList<>(); - for (int i = 0; i < headers.size(); i++) { - final Header header = new Header(headers.name(i), headers.value(i)); - final Header modifiedHeader = modifyHeader(header, headerModifiers); - if (modifiedHeader != null) { - modifiableHeaders.add(modifiedHeader); - } - } - this.headers = Collections.unmodifiableList(modifiableHeaders); - } - - private Header modifyHeader(Header header, List headerModifiers) { - for (HeaderModifier modifier : headerModifiers) { - if (modifier.matches(header)) { - return modifier.modify(header); - } - } - - return header; - } - - private String getContentType(RequestBody body) { - final MediaType mediaType = body.contentType(); - if (mediaType != null) { - return mediaType.toString(); - } - - return null; - } - - private String getBodyAsString(RequestBody body, long limit) { - try { - final Buffer sink = new Buffer(); - - final MediaType mediaType = body.contentType(); - final Charset charset = getCharset(mediaType); - - if (limit > 0) { - final BufferedSink buffer = Okio.buffer(new LimitedSink(sink, limit)); - body.writeTo(buffer); - buffer.flush(); - } else { - body.writeTo(sink); - } - - return sink.readString(charset); - } catch (IOException e) { - return "Error while reading body: " + e.toString(); - } - } - - private Charset getCharset(MediaType mediaType) { - if (mediaType != null) { - return mediaType.charset(Charset.defaultCharset()); - } - - return Charset.defaultCharset(); - } - - public String build() { - List parts = new ArrayList<>(); - parts.add("curl"); - parts.addAll(options); - parts.add(String.format(FORMAT_METHOD, method.toUpperCase())); - - for (Header header : headers) { - final String headerPart = String.format(FORMAT_HEADER, header.name(), header.value()); - parts.add(headerPart); - } - - if (contentType != null && !containsName(CONTENT_TYPE, headers)) { - parts.add(String.format(FORMAT_HEADER, CONTENT_TYPE, contentType)); - } - - if (body != null) { - parts.add(String.format(FORMAT_BODY, body)); - } - - parts.add(String.format(FORMAT_URL, url)); - - return join(delimiter, parts); - } - - protected boolean containsName(String name, List
headers) { - for (Header header : headers) { - if (header.name().equals(name)) { - return true; - } - } - - return false; - } -} diff --git a/ok2curl/src/main/java/com/moczul/ok2curl/CurlCommandGenerator.kt b/ok2curl/src/main/java/com/moczul/ok2curl/CurlCommandGenerator.kt new file mode 100644 index 0000000..1e8266c --- /dev/null +++ b/ok2curl/src/main/java/com/moczul/ok2curl/CurlCommandGenerator.kt @@ -0,0 +1,111 @@ +package com.moczul.ok2curl + +import okhttp3.Headers +import okhttp3.HttpUrl +import okhttp3.MediaType +import okhttp3.Request +import okhttp3.RequestBody +import okio.Buffer +import okio.buffer +import java.io.IOException +import java.nio.charset.Charset +import java.util.Locale + +class CurlCommandGenerator(private val configuration: Configuration) { + + fun generate(request: Request): String { + return configuration.components + .flatMap { generateCommandComponent(it, request) } + .joinToString(separator = configuration.delimiter) + } + + private fun generateCommandComponent( + commandComponent: CommandComponent, + request: Request + ): List { + return when (commandComponent) { + CommandComponent.Curl -> listOf("curl") + CommandComponent.Url -> generateUrl(request.url) + CommandComponent.Flags -> generateFlags() + CommandComponent.Body -> generateBody(request.body) + CommandComponent.Method -> generateMethod(request.method) + CommandComponent.Header -> generateHeaders(request.headers, request.body) + } + } + + private fun generateBody(body: RequestBody?): List { + return if (body != null) { + val bodyString = getBodyAsString(body) + listOf(bodyString) + } else { + emptyList() + } + } + + private fun getBodyAsString(body: RequestBody) = try { + val sink = Buffer() + val mediaType = body.contentType() + val charset: Charset = getCharset(mediaType) + if (configuration.limit > 0) { + val buffer = LimitedSink(sink, configuration.limit).buffer() + body.writeTo(buffer) + buffer.flush() + } else { + body.writeTo(sink) + } + FORMAT_BODY.format(sink.readString(charset)) + } catch (e: IOException) { + "Error while reading body: $e" + } + + private fun generateHeaders(headers: Headers, body: RequestBody?): List { + return headers + .map { Header(name = it.first, value = it.second) } + .mapNotNull { header -> modifyHeader(header) } + .applyContentTypeHeader(body) + .map { header -> FORMAT_HEADER.format(header.name, header.value) } + } + + + private fun modifyHeader(header: Header): Header? { + val modifier = configuration.headerModifiers.find { it.matches(header) } + return if (modifier != null) { + modifier.modify(header) + } else { + header + } + } + + private fun generateMethod(method: String): List { + return listOf(FORMAT_METHOD.format(method.uppercase(Locale.getDefault()))) + } + + private fun generateFlags(): List = configuration.flags.list() + + private fun generateUrl(url: HttpUrl): List = listOf(FORMAT_URL.format(url.toString())) + + private fun getCharset(mediaType: MediaType?): Charset { + val default = Charset.defaultCharset() + return mediaType?.charset(default) ?: default + } + + + private fun List
.applyContentTypeHeader(body: RequestBody?): List
{ + val contentTypeHeader = find { it.name.equals(CONTENT_TYPE, ignoreCase = false) } + val contentType = body?.contentType()?.toString() + + return if (contentTypeHeader == null && contentType != null) { + this + listOf(Header(CONTENT_TYPE, contentType)) + } else { + this + } + } + + private companion object { + const val FORMAT_HEADER = "-H \"%1\$s:%2\$s\"" + const val FORMAT_METHOD = "-X %1\$s" + const val FORMAT_BODY = "-d '%1\$s'" + const val FORMAT_URL = "\"%1\$s\"" + const val CONTENT_TYPE = "Content-Type" + } +} \ No newline at end of file diff --git a/ok2curl/src/main/java/com/moczul/ok2curl/CurlInterceptor.java b/ok2curl/src/main/java/com/moczul/ok2curl/CurlInterceptor.java deleted file mode 100644 index 0b157db..0000000 --- a/ok2curl/src/main/java/com/moczul/ok2curl/CurlInterceptor.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.moczul.ok2curl; - -import com.moczul.ok2curl.logger.Loggable; -import com.moczul.ok2curl.modifier.HeaderModifier; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import okhttp3.Interceptor; -import okhttp3.Request; -import okhttp3.Response; - -public class CurlInterceptor implements Interceptor { - - private static final long DEFAULT_LIMIT = 1024L * 1024L; - private static final String DEFAULT_DELIMITER = " "; - - protected final Loggable logger; - protected final long limit; - protected final List headerModifiers = new ArrayList<>(); - protected final Options options; - protected final String delimiter; - - /** - * Interceptor responsible for printing curl logs - * - * Logs are pushed to stdout with 1MB limit - * - * @param logger output of logging - */ - public CurlInterceptor(Loggable logger) { - this(logger, DEFAULT_LIMIT, Collections.emptyList(), Options.EMPTY, DEFAULT_DELIMITER); - } - - /** - * Interceptor responsible for printing curl logs - * - * Logs are pushed to stdout with 1MB limit - * - * @param logger output of logging - * @param options list of curl options - */ - public CurlInterceptor(Loggable logger, Options options) { - this(logger, DEFAULT_LIMIT, Collections.emptyList(), options, DEFAULT_DELIMITER); - } - - /** - * Interceptor responsible for printing curl logs - * - * @param logger output of logging - * @param headerModifiers list of header modifiers - */ - public CurlInterceptor(Loggable logger, List headerModifiers) { - this(logger, DEFAULT_LIMIT, headerModifiers, Options.EMPTY, DEFAULT_DELIMITER); - } - - /** - * Interceptor responsible for printing curl logs - * - * @param logger output of logging - * @param limit limit maximal bytes logged, if negative - non limited - */ - public CurlInterceptor(Loggable logger, long limit) { - this(logger, limit, Collections.emptyList(), Options.EMPTY, DEFAULT_DELIMITER); - } - - /** - * Interceptor responsible for printing curl logs - * @param logger output of logging - * @param limit limit maximal bytes logged, if negative - non limited - * @param headerModifiers list of header modifiers - * @param options list of curl options - */ - public CurlInterceptor(Loggable logger, long limit, List headerModifiers, Options options) { - this(logger, limit, headerModifiers, options, DEFAULT_DELIMITER); - } - - /** - * Interceptor responsible for printing curl logs - * @param logger output of logging - * @param limit limit maximal bytes logged, if negative - non limited - * @param headerModifiers list of header modifiers - * @param options list of curl options - * @param delimiter string delimiter - */ - public CurlInterceptor(Loggable logger, long limit, List headerModifiers, Options options, String delimiter) { - this.logger = logger; - this.limit = limit; - this.headerModifiers.addAll(headerModifiers); - this.options = options; - this.delimiter = delimiter; - } - - @Override - public Response intercept(Chain chain) throws IOException { - final Request request = chain.request(); - - final Request copy = request.newBuilder().build(); - final String curl = getCurlBuilder(copy).build(); - - logger.log(curl); - - return chain.proceed(request); - } - - protected CurlBuilder getCurlBuilder(Request copy) { - return new CurlBuilder(copy, limit, headerModifiers, options, delimiter); - } -} diff --git a/ok2curl/src/main/java/com/moczul/ok2curl/CurlInterceptor.kt b/ok2curl/src/main/java/com/moczul/ok2curl/CurlInterceptor.kt new file mode 100644 index 0000000..0fde228 --- /dev/null +++ b/ok2curl/src/main/java/com/moczul/ok2curl/CurlInterceptor.kt @@ -0,0 +1,22 @@ +package com.moczul.ok2curl + +import com.moczul.ok2curl.logger.Logger +import okhttp3.Interceptor +import okhttp3.Response + +class CurlInterceptor @JvmOverloads constructor( + private val logger: Logger, + configuration: Configuration = Configuration() +) : Interceptor { + + private val curlGenerator = CurlCommandGenerator(configuration) + + override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request() + + val curl = curlGenerator.generate(request) + logger.log(curl) + + return chain.proceed(request) + } +} \ No newline at end of file diff --git a/ok2curl/src/main/java/com/moczul/ok2curl/Flags.kt b/ok2curl/src/main/java/com/moczul/ok2curl/Flags.kt new file mode 100644 index 0000000..043abfc --- /dev/null +++ b/ok2curl/src/main/java/com/moczul/ok2curl/Flags.kt @@ -0,0 +1,59 @@ +package com.moczul.ok2curl + +import java.util.Locale + +class Flags private constructor(options: Collection) { + + private val options: List + + init { + this.options = options.toList() + } + + fun list(): List { + return options + } + + class Builder { + private val options: MutableSet = mutableSetOf() + fun insecure(): Builder { + options.add("--insecure") + return this + } + + fun maxTime(seconds: Int): Builder { + options.add(String.format(Locale.getDefault(), "--max-time %d", seconds)) + return this + } + + fun connectTimeout(seconds: Int): Builder { + options.add(String.format(Locale.getDefault(), "--connect-timeout %d", seconds)) + return this + } + + fun retry(num: Int): Builder { + options.add(String.format(Locale.getDefault(), "--retry %d", num)) + return this + } + + fun compressed(): Builder { + options.add("--compressed") + return this + } + + fun location(): Builder { + options.add("--location") + return this + } + + fun build() = Flags(options) + } + + companion object { + @JvmField + val EMPTY = Flags(emptyList()) + + @JvmStatic + fun builder() = Builder() + } +} \ No newline at end of file diff --git a/ok2curl/src/main/java/com/moczul/ok2curl/Header.java b/ok2curl/src/main/java/com/moczul/ok2curl/Header.java deleted file mode 100644 index 2b9a825..0000000 --- a/ok2curl/src/main/java/com/moczul/ok2curl/Header.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.moczul.ok2curl; - -public class Header { - - private final String name; - private final String value; - - public Header(String name, String value) { - this.name = name; - this.value = value; - } - - public String name() { - return name; - } - - public String value() { - return value; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Header header = (Header) o; - - if (name != null ? !name.equals(header.name) : header.name != null) return false; - return value != null ? value.equals(header.value) : header.value == null; - - } - - @Override - public int hashCode() { - int result = name != null ? name.hashCode() : 0; - result = 31 * result + (value != null ? value.hashCode() : 0); - return result; - } -} diff --git a/ok2curl/src/main/java/com/moczul/ok2curl/Header.kt b/ok2curl/src/main/java/com/moczul/ok2curl/Header.kt new file mode 100644 index 0000000..882b856 --- /dev/null +++ b/ok2curl/src/main/java/com/moczul/ok2curl/Header.kt @@ -0,0 +1,3 @@ +package com.moczul.ok2curl + +data class Header(val name: String, val value: String) \ No newline at end of file diff --git a/ok2curl/src/main/java/com/moczul/ok2curl/LimitedSink.java b/ok2curl/src/main/java/com/moczul/ok2curl/LimitedSink.java deleted file mode 100644 index a0d3218..0000000 --- a/ok2curl/src/main/java/com/moczul/ok2curl/LimitedSink.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.moczul.ok2curl; - -import java.io.IOException; - -import okio.Buffer; -import okio.Sink; -import okio.Timeout; - -public class LimitedSink implements Sink { - - private final Buffer limited; - private long total; - - public LimitedSink(Buffer limited, long limit) { - if (limited == null) throw new NullPointerException("limited can not be null"); - if (limit <= 0) throw new IllegalArgumentException("limit has to be grater than 0"); - this.limited = limited; - total = limit; - } - - @Override - public void write(Buffer source, long byteCount) throws IOException { - if (total > 0) { - long toWrite = Math.min(total, byteCount); - limited.write(source, toWrite); - total -= toWrite; - } - } - - @Override - public void flush() throws IOException { - limited.flush(); - } - - @Override - public Timeout timeout() { - return Timeout.NONE; - } - - @Override - public void close() throws IOException { - limited.close(); - } -} diff --git a/ok2curl/src/main/java/com/moczul/ok2curl/LimitedSink.kt b/ok2curl/src/main/java/com/moczul/ok2curl/LimitedSink.kt new file mode 100644 index 0000000..f3d8a4f --- /dev/null +++ b/ok2curl/src/main/java/com/moczul/ok2curl/LimitedSink.kt @@ -0,0 +1,42 @@ +package com.moczul.ok2curl + +import okio.Buffer +import okio.Sink +import okio.Timeout +import java.io.IOException +import kotlin.math.min + +internal class LimitedSink(limited: Buffer, limit: Long) : Sink { + + private val limited: Buffer + private var total: Long + + init { + require(limit > 0) { "limit has to be grater than 0" } + this.limited = limited + this.total = limit + } + + @Throws(IOException::class) + override fun write(source: Buffer, byteCount: Long) { + if (total > 0) { + val toWrite = min(total, byteCount) + limited.write(source, toWrite) + total -= toWrite + } + } + + @Throws(IOException::class) + override fun flush() { + limited.flush() + } + + override fun timeout(): Timeout { + return Timeout.NONE + } + + @Throws(IOException::class) + override fun close() { + limited.close() + } +} \ No newline at end of file diff --git a/ok2curl/src/main/java/com/moczul/ok2curl/Options.java b/ok2curl/src/main/java/com/moczul/ok2curl/Options.java deleted file mode 100644 index bbc1c62..0000000 --- a/ok2curl/src/main/java/com/moczul/ok2curl/Options.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.moczul.ok2curl; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Set; - -public class Options { - - public static final Options EMPTY = new Options(Collections.emptyList()); - - private final List options; - - public static Builder builder() { - return new Builder(); - } - - private Options(Collection options) { - this.options = new ArrayList<>(options); - } - - public List list() { - return options; - } - - public static class Builder { - - private final Set options = new HashSet<>(); - - public Builder insecure() { - options.add("--insecure"); - return this; - } - - public Builder maxTime(int seconds) { - options.add(String.format(Locale.getDefault(), "--max-time %d", seconds)); - return this; - } - - public Builder connectTimeout(int seconds) { - options.add(String.format(Locale.getDefault(), "--connect-timeout %d", seconds)); - return this; - } - - public Builder retry(int num) { - options.add(String.format(Locale.getDefault(), "--retry %d", num)); - return this; - } - - public Builder compressed() { - options.add("--compressed"); - return this; - } - - public Builder location() { - options.add("--location"); - return this; - } - - public Options build() { - return new Options(options); - } - } - -} diff --git a/ok2curl/src/main/java/com/moczul/ok2curl/StringUtil.java b/ok2curl/src/main/java/com/moczul/ok2curl/StringUtil.java deleted file mode 100644 index 450432f..0000000 --- a/ok2curl/src/main/java/com/moczul/ok2curl/StringUtil.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.moczul.ok2curl; - -public class StringUtil { - - /** - * Returns a string containing the tokens joined by delimiters. - * @param tokens an array objects to be joined. Strings will be formed from - * the objects by calling object.toString(). - */ - public static String join(CharSequence delimiter, Iterable tokens) { - StringBuilder sb = new StringBuilder(); - boolean firstTime = true; - for (Object token: tokens) { - if (firstTime) { - firstTime = false; - } else { - sb.append(delimiter); - } - sb.append(token); - } - return sb.toString(); - } -} diff --git a/ok2curl/src/main/java/com/moczul/ok2curl/logger/Loggable.java b/ok2curl/src/main/java/com/moczul/ok2curl/logger/Loggable.java deleted file mode 100644 index 9082b75..0000000 --- a/ok2curl/src/main/java/com/moczul/ok2curl/logger/Loggable.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.moczul.ok2curl.logger; - -public interface Loggable { - - void log(String message); -} diff --git a/ok2curl/src/main/java/com/moczul/ok2curl/logger/Logger.kt b/ok2curl/src/main/java/com/moczul/ok2curl/logger/Logger.kt new file mode 100644 index 0000000..d2cea17 --- /dev/null +++ b/ok2curl/src/main/java/com/moczul/ok2curl/logger/Logger.kt @@ -0,0 +1,5 @@ +package com.moczul.ok2curl.logger + +interface Logger { + fun log(message: String) +} \ No newline at end of file diff --git a/ok2curl/src/main/java/com/moczul/ok2curl/modifier/HeaderModifier.java b/ok2curl/src/main/java/com/moczul/ok2curl/modifier/HeaderModifier.kt similarity index 65% rename from ok2curl/src/main/java/com/moczul/ok2curl/modifier/HeaderModifier.java rename to ok2curl/src/main/java/com/moczul/ok2curl/modifier/HeaderModifier.kt index a53886f..29cec21 100644 --- a/ok2curl/src/main/java/com/moczul/ok2curl/modifier/HeaderModifier.java +++ b/ok2curl/src/main/java/com/moczul/ok2curl/modifier/HeaderModifier.kt @@ -1,21 +1,20 @@ -package com.moczul.ok2curl.modifier; +package com.moczul.ok2curl.modifier -import com.moczul.ok2curl.Header; +import com.moczul.ok2curl.Header /** * HeaderModifier allow for changing header name/value before creating curl log */ -public interface HeaderModifier { - +interface HeaderModifier { /** * @param header the header to check * @return true if header should be modified and false otherwise. */ - boolean matches(Header header); + fun matches(header: Header): Boolean /** * @param header the header to modify * @return modified header or null to omit header in curl log */ - Header modify(Header header); -} + fun modify(header: Header): Header? +} \ No newline at end of file diff --git a/ok2curl/src/test/java/com/moczul/ok2curl/CookieHandlerTest.java b/ok2curl/src/test/java/com/moczul/ok2curl/CookieHandlerTest.java deleted file mode 100644 index b4da299..0000000 --- a/ok2curl/src/test/java/com/moczul/ok2curl/CookieHandlerTest.java +++ /dev/null @@ -1,130 +0,0 @@ -package com.moczul.ok2curl; - -import com.moczul.ok2curl.util.FakeLogger; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentCaptor; - -import java.io.IOException; -import java.util.Arrays; -import java.util.List; - -import okhttp3.Cookie; -import okhttp3.CookieJar; -import okhttp3.HttpUrl; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import okio.BufferedSink; - -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.contains; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -public class CookieHandlerTest { - - private FakeLogger logger; - private MockWebServer server; - private String url; - - @Before - public void setUpOkHttp() throws IOException { - logger = mock(FakeLogger.class); - - server = new MockWebServer(); - server.start(); - server.enqueue(new MockResponse().setBody("ok")); - url = server.url("/test").toString(); - } - - @After - public void tearDown() throws IOException { - server.shutdown(); - } - - @Test - public void testIfRequestBodyThrowsException_returnInfoAboutError() throws Exception { - final Request request = new Request.Builder().url(url).patch(new RequestBody() { - @Override - public MediaType contentType() { - return MediaType.parse("application/json"); - } - - @Override - public void writeTo(BufferedSink sink) throws IOException { - throw new IOException("exception"); - } - }).build(); - - OkHttpClient okHttpClient = new OkHttpClient.Builder() - .addInterceptor(new CurlInterceptor(logger)) - .cookieJar(getCookieJar()) - .build(); - - try { - okHttpClient.newCall(request).execute(); - } catch (IOException ignore) {} - - final ArgumentCaptor stringArgumentCaptor = ArgumentCaptor.forClass(String.class); - verify(logger).log(stringArgumentCaptor.capture()); - assertTrue(stringArgumentCaptor.getValue().contains("Error")); - } - - @Test - public void testApplicationInterceptor() throws IOException { - final Request request = new Request.Builder().url(url).build(); - - OkHttpClient okHttpClient = new OkHttpClient.Builder() - .addInterceptor(new CurlInterceptor(logger)) - .cookieJar(getCookieJar()) - .build(); - - okHttpClient.newCall(request).execute(); - - final String expectedCurl = String.format("curl -X GET \"%s\"", url); - verify(logger).log(expectedCurl); - } - - @Test - public void testNetworkInterceptor() throws IOException { - final Request request = new Request.Builder().url(url).build(); - - OkHttpClient okHttpClient = new OkHttpClient.Builder() - .addNetworkInterceptor(new CurlInterceptor(logger)) - .cookieJar(getCookieJar()) - .build(); - - okHttpClient.newCall(request).execute(); - - verify(logger).log(contains("-H \"Cookie:foo=bar; banana=rama\"")); - } - - private CookieJar getCookieJar() { - return new CookieJar() { - @Override - public void saveFromResponse(HttpUrl url, List cookies) { - } - - @Override - public List loadForRequest(HttpUrl url) { - Cookie foo = new Cookie.Builder() - .name("foo") - .value("bar") - .domain(url.host()) - .build(); - Cookie banana = new Cookie.Builder() - .name("banana") - .value("rama") - .domain(url.host()) - .build(); - return Arrays.asList(foo, banana); - } - }; - } -} diff --git a/ok2curl/src/test/java/com/moczul/ok2curl/CookieHandlerTest.kt b/ok2curl/src/test/java/com/moczul/ok2curl/CookieHandlerTest.kt new file mode 100644 index 0000000..d0460a6 --- /dev/null +++ b/ok2curl/src/test/java/com/moczul/ok2curl/CookieHandlerTest.kt @@ -0,0 +1,120 @@ +package com.moczul.ok2curl + +import com.moczul.ok2curl.logger.Logger +import okhttp3.Cookie +import okhttp3.CookieJar +import okhttp3.HttpUrl +import okhttp3.MediaType +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.RequestBody +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import okio.BufferedSink +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.mockito.ArgumentMatchers.contains +import org.mockito.Mockito.mock +import org.mockito.Mockito.verify +import java.io.IOException + +class CookieHandlerTest { + + private val logger: Logger = mock(Logger::class.java) + private lateinit var server: MockWebServer + private lateinit var url: String + + @Before + fun setUpOkHttp() { + server = MockWebServer() + server.start() + server.enqueue(MockResponse().setBody("ok")) + url = server.url("/test").toString() + } + + @After + fun tearDown() { + server.shutdown() + } + + @Test + fun `should intercept failed request and log error information`() { + // given + val request: Request = Request.Builder().url(url).patch(object : RequestBody() { + override fun contentType(): MediaType? { + return "application/json".toMediaTypeOrNull() + } + + override fun writeTo(sink: BufferedSink) { + throw IOException("exception") + } + }).build() + val okHttpClient = OkHttpClient.Builder() + .addInterceptor(CurlInterceptor(logger)) + .cookieJar(createCookieJar()) + .build() + + // when + try { + okHttpClient.newCall(request).execute() + } catch (ignore: IOException) { + } + + // then + verify(logger).log(contains("Error")) + } + + @Test + fun `should intercept request with application interceptor`() { + // given + val request: Request = Request.Builder().url(url).build() + val okHttpClient = OkHttpClient.Builder() + .addInterceptor(CurlInterceptor(logger)) + .cookieJar(createCookieJar()) + .build() + + // when + okHttpClient.newCall(request).execute() + + // then + val expectedCurl = String.format("curl -X GET \"%s\"", url) + verify(logger).log(expectedCurl) + } + + @Test + fun `should intercept request with network interceptor`() { + // given + val request: Request = Request.Builder().url(url).build() + val okHttpClient = OkHttpClient.Builder() + .addNetworkInterceptor(CurlInterceptor(logger)) + .cookieJar(createCookieJar()) + .build() + + // when + okHttpClient.newCall(request).execute() + + // then + verify(logger).log(contains("-H \"Cookie:foo=bar; banana=rama\"")) + } + + private fun createCookieJar(): CookieJar { + return object : CookieJar { + override fun saveFromResponse(url: HttpUrl, cookies: List) {} + override fun loadForRequest(url: HttpUrl): List { + val foo: Cookie = Cookie.Builder() + .name("foo") + .value("bar") + .domain(url.host) + .build() + val banana: Cookie = Cookie.Builder() + .name("banana") + .value("rama") + .domain(url.host) + .build() + return listOf(foo, banana) + } + } + } +} \ No newline at end of file diff --git a/ok2curl/src/test/java/com/moczul/ok2curl/CurlBuilderTest.java b/ok2curl/src/test/java/com/moczul/ok2curl/CurlBuilderTest.java deleted file mode 100644 index 3d2c318..0000000 --- a/ok2curl/src/test/java/com/moczul/ok2curl/CurlBuilderTest.java +++ /dev/null @@ -1,123 +0,0 @@ -package com.moczul.ok2curl; - -import com.moczul.ok2curl.modifier.HeaderModifier; - -import org.junit.Test; - -import java.util.Collections; -import java.util.concurrent.TimeUnit; - -import okhttp3.CacheControl; -import okhttp3.FormBody; -import okhttp3.Request; -import okhttp3.RequestBody; - -import static org.junit.Assert.assertEquals; - -public class CurlBuilderTest { - - @Test - public void getRequestHasCorrectCommand() { - final Request request = new Request.Builder().url("http://example.com/").build(); - - final String command = new CurlBuilder(request).build(); - - assertEquals("curl -X GET \"http://example.com/\"", command); - } - - @Test - public void getRequestHasCorrectHeader() { - final Request request = new Request.Builder() - .url("http://example.com/") - .header("Accept", "application/json") - .build(); - - final String command = new CurlBuilder(request).build(); - - assertEquals("curl -X GET -H \"Accept:application/json\" \"http://example.com/\"", command); - } - - @Test - public void getRequestHasCorrectCacheHeader() { - final CacheControl cache = new CacheControl.Builder() - .maxAge(1, TimeUnit.DAYS) - .onlyIfCached() - .build(); - - final Request request = new Request.Builder() - .url("http://example.com/") - .cacheControl(cache) - .build(); - - final String command = new CurlBuilder(request).build(); - - assertEquals("curl -X GET -H \"Cache-Control:max-age=86400, only-if-cached\" \"http://example.com/\"", command); - } - - @Test - public void postRequestHasCorrectPostData() { - final Request request = new Request.Builder().url("http://example.com/").post(body()).build(); - - final String command = new CurlBuilder(request).build(); - - final String expected = "curl -X POST -H \"Content-Type:application/x-www-form-urlencoded\" -d 'key1=value1' \"http://example.com/\""; - assertEquals(expected, command); - } - - @Test - public void postRequestBodyWithNullMediaType() { - final RequestBody body = RequestBody.create(null, "StringBody"); - final Request request = new Request.Builder().url("http://example.com/").post(body).build(); - - final String command = new CurlBuilder(request).build(); - - final String expected = "curl -X POST -d 'StringBody' \"http://example.com/\""; - assertEquals(expected, command); - } - - @Test - public void multipleHeadersWithTheSameNameShouldBeAddedToCurlCommand() { - final Request request = new Request.Builder() - .url("http://example.com/") - .addHeader("Cookie", "FIRST=foo") - .addHeader("Cookie", "SECOND=bar") - .build(); - - final String command = new CurlBuilder(request).build(); - - assertEquals("curl -X GET -H \"Cookie:FIRST=foo\" -H \"Cookie:SECOND=bar\" \"http://example.com/\"", command); - } - - @Test - public void getRequestContainsInsecureOption() { - final Request request = new Request.Builder().url("http://example.com/").build(); - final Options options = Options.builder().insecure().build(); - - final String command = new CurlBuilder(request, 1024, Collections.emptyList(), options).build(); - - assertEquals("curl --insecure -X GET \"http://example.com/\"", command); - } - - @Test - public void getRequestContainsConnectTimeoutOptions() { - final Request request = new Request.Builder().url("http://example.com/").build(); - final Options options = Options.builder().connectTimeout(120).build(); - - final String command = new CurlBuilder(request, 1024, Collections.emptyList(), options).build(); - - assertEquals("curl --connect-timeout 120 -X GET \"http://example.com/\"", command); - } - - @Test - public void getRequesWithDelimiter() { - final Request request = new Request.Builder().url("http://example.com/").build(); - - final String command = new CurlBuilder(request, -1L, Collections.emptyList(), Options.EMPTY, " \\\n").build(); - - assertEquals("curl \\\n-X GET \\\n\"http://example.com/\"", command); - } - - private RequestBody body() { - return new FormBody.Builder().add("key1", "value1").build(); - } -} diff --git a/ok2curl/src/test/java/com/moczul/ok2curl/CurlCommandGeneratorTest.kt b/ok2curl/src/test/java/com/moczul/ok2curl/CurlCommandGeneratorTest.kt new file mode 100644 index 0000000..946c7f1 --- /dev/null +++ b/ok2curl/src/test/java/com/moczul/ok2curl/CurlCommandGeneratorTest.kt @@ -0,0 +1,186 @@ +package com.moczul.ok2curl + +import okhttp3.CacheControl +import okhttp3.FormBody +import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody +import org.junit.Assert.assertEquals +import org.junit.Test +import java.util.concurrent.TimeUnit + + +class CurlCommandGeneratorTest { + + private val configuration = Configuration() + + @Test + fun `should generate simple GET command`() { + // given + val curlGenerator = CurlCommandGenerator(configuration) + val request: Request = Request.Builder().url("http://example.com/").build() + + // when + val command = curlGenerator.generate(request) + + // then + assertEquals("curl -X GET \"http://example.com/\"", command) + } + + @Test + fun `should generate get command with headers`() { + // given + val curlGenerator = CurlCommandGenerator(configuration) + val request: Request = Request.Builder() + .url("http://example.com/") + .header("Accept", "application/json") + .build() + + // when + val command = curlGenerator.generate(request) + + // then + assertEquals("curl -X GET -H \"Accept:application/json\" \"http://example.com/\"", command) + } + + @Test + fun `should generate GET command with cache-control header`() { + // given + val curlGenerator = CurlCommandGenerator(configuration) + val request: Request = Request.Builder() + .url("http://example.com/") + .cacheControl(oneDayCache()) + .build() + + // when + val command = curlGenerator.generate(request) + + // then + assertEquals( + "curl -X GET -H \"Cache-Control:max-age=86400, only-if-cached\" \"http://example.com/\"", + command + ) + } + + @Test + fun `should generate POST command with body`() { + // given + val curlGenerator = CurlCommandGenerator(configuration) + val request: Request = Request.Builder().url("http://example.com/").post(body()).build() + + // when + val command = curlGenerator.generate(request) + + // then + val expected = + "curl -X POST -H \"Content-Type:application/x-www-form-urlencoded\" -d 'key1=value1' \"http://example.com/\"" + assertEquals(expected, command) + } + + @Test + fun postRequestBodyWithNullMediaType() { + // given + val curlGenerator = CurlCommandGenerator(configuration) + val body = "StringBody".toRequestBody(contentType = null) + val request: Request = Request.Builder().url("http://example.com/").post(body).build() + + // when + val command = curlGenerator.generate(request) + + // then + val expected = "curl -X POST -d 'StringBody' \"http://example.com/\"" + assertEquals(expected, command) + } + + @Test + fun `should generate GET command with mutli-value header`() { + // given + val curlGenerator = CurlCommandGenerator(configuration) + val request: Request = Request.Builder() + .url("http://example.com/") + .addHeader("Cookie", "FIRST=foo") + .addHeader("Cookie", "SECOND=bar") + .build() + + // when + val command = curlGenerator.generate(request) + + // when + assertEquals( + "curl -X GET -H \"Cookie:FIRST=foo\" -H \"Cookie:SECOND=bar\" \"http://example.com/\"", + command + ) + } + + @Test + fun `should generate GET command with insecure flag`() { + // given + val insecureConfig = Configuration(flags = Flags.builder().insecure().build()) + val curlGenerator = CurlCommandGenerator(insecureConfig) + val request: Request = Request.Builder().url("http://example.com/").build() + + // when + val command = curlGenerator.generate(request) + + // then + assertEquals("curl --insecure -X GET \"http://example.com/\"", command) + } + + @Test + fun `should generate GET command with connect-timeout flag`() { + // given + val timeoutConfig = Configuration(flags = Flags.builder().connectTimeout(120).build()) + val curlGenerator = CurlCommandGenerator(timeoutConfig) + val request: Request = Request.Builder().url("http://example.com/").build() + + // when + val command = curlGenerator.generate(request) + + // †hen + assertEquals("curl --connect-timeout 120 -X GET \"http://example.com/\"", command) + } + + @Test + fun `should generate GET command using specific delimiter`() { + // given + val delimiterConfig = Configuration(delimiter = " \\\n") + val curlGenerator = CurlCommandGenerator(delimiterConfig) + val request: Request = Request.Builder().url("http://example.com/").build() + + // when + val command = curlGenerator.generate(request) + + // then + assertEquals("curl \\\n-X GET \\\n\"http://example.com/\"", command) + } + + @Test + fun `should generate POST command with specified components`() { + // given + val simpleConfig = Configuration( + components = listOf( + CommandComponent.Curl, + CommandComponent.Method, + CommandComponent.Url + ) + ) + val curlGenerator = CurlCommandGenerator(simpleConfig) + val request: Request = Request.Builder() + .url("https://github.com") + .cacheControl(oneDayCache()) + .post(body()) + .build() + + // when + val command = curlGenerator.generate(request) + + // then + assertEquals("curl -X POST \"https://github.com/\"", command) + } + + private companion object { + fun body() = FormBody.Builder().add("key1", "value1").build() + + fun oneDayCache() = + CacheControl.Builder().maxAge(1, TimeUnit.DAYS).onlyIfCached().build() + } +} \ No newline at end of file diff --git a/ok2curl/src/test/java/com/moczul/ok2curl/FlagsTest.kt b/ok2curl/src/test/java/com/moczul/ok2curl/FlagsTest.kt new file mode 100644 index 0000000..ceefb01 --- /dev/null +++ b/ok2curl/src/test/java/com/moczul/ok2curl/FlagsTest.kt @@ -0,0 +1,59 @@ +package com.moczul.ok2curl + +import org.hamcrest.CoreMatchers.`is` +import org.hamcrest.MatcherAssert.assertThat +import org.junit.Assert.assertEquals +import org.junit.Test + +class FlagsTest { + + @Test + fun `should return empty options for default builder`() { + // when + val flags = Flags.builder().build() + + // when + assertThat(flags.list().size, `is`(0)) + } + + @Test + fun `should correctly handle options parameters`() { + // when + val flags = Flags.builder() + .maxTime(120) + .connectTimeout(60) + .retry(3) + .build() + + // then + val expectedFlags = listOf("--max-time 120", "--connect-timeout 60", "--retry 3") + assertEquals(expectedFlags, flags.list()) + } + + @Test + fun `should return correct list of parameters`() { + // when + val flags = Flags.builder() + .insecure() + .compressed() + .location() + .build() + + // then + val expectedFlags = listOf("--insecure", "--compressed", "--location") + assertEquals(expectedFlags, flags.list()) + } + + @Test + fun `should ignore duplicated parameters`() { + // when + val flags = Flags.builder() + .insecure() + .insecure() + .insecure() + .build() + + // then + assertEquals(listOf("--insecure"), flags.list()) + } +} \ No newline at end of file diff --git a/ok2curl/src/test/java/com/moczul/ok2curl/LimitedSinkTest.java b/ok2curl/src/test/java/com/moczul/ok2curl/LimitedSinkTest.java deleted file mode 100644 index 7f82327..0000000 --- a/ok2curl/src/test/java/com/moczul/ok2curl/LimitedSinkTest.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.moczul.ok2curl; - -import org.junit.Test; - -import java.nio.charset.Charset; - -import okio.Buffer; -import okio.BufferedSink; -import okio.ByteString; -import okio.Okio; - -import static org.junit.Assert.*; - -public class LimitedSinkTest { - - @Test(expected = IllegalArgumentException.class) - public void testWhenLimitIs0_throwIllegalArgumentException() throws Exception { - new LimitedSink(new Buffer(), 0L); - } - - @Test(expected = IllegalArgumentException.class) - public void testWhenLimitIsNegative_throwIllegalArgumentException() throws Exception { - new LimitedSink(new Buffer(), -1L); - } - - @Test(expected = NullPointerException.class) - public void testWhenBufferIsNull_throwNullPointerException() throws Exception { - new LimitedSink(null, 10L); - } - - @Test - public void testWhenLongResult_writeOnlyLimitedBytes() throws Exception { - final Buffer limited = new Buffer(); - final BufferedSink source = Okio.buffer(new LimitedSink(limited, 10L)); - - source - .write(ByteString.encodeUtf8("0123456789012345678901234567890123456789")); // 40B - source.flush(); - - assertEquals("0123456789", limited.readString(Charset.forName("UTF-8"))); - } - - @Test - public void testWhenLongResultWrittenInParts_writeOnlyLimitedBytes() throws Exception { - final Buffer limited = new Buffer(); - final BufferedSink source = Okio.buffer(new LimitedSink(limited, 10L)); - - source - .write(ByteString.encodeUtf8("01234")); // 5B - source.flush(); - source - .write(ByteString.encodeUtf8("5678901234")); // 10B - source.flush(); - source - .write(ByteString.encodeUtf8("5678901234567890123456789")); // 25B - source.flush(); - - assertEquals("0123456789", limited.readString(Charset.forName("UTF-8"))); - } - - @Test - public void testWhenShorterResult_writeOnlyThatData() throws Exception { - final Buffer limited = new Buffer(); - final BufferedSink source = Okio.buffer(new LimitedSink(limited, 10L)); - - source - .write(ByteString.encodeUtf8("01234")); // 5B - source.flush(); - - assertEquals("01234", limited.readString(Charset.forName("UTF-8"))); - } -} diff --git a/ok2curl/src/test/java/com/moczul/ok2curl/LimitedSinkTest.kt b/ok2curl/src/test/java/com/moczul/ok2curl/LimitedSinkTest.kt new file mode 100644 index 0000000..254b5fb --- /dev/null +++ b/ok2curl/src/test/java/com/moczul/ok2curl/LimitedSinkTest.kt @@ -0,0 +1,67 @@ +package com.moczul.ok2curl + +import okio.Buffer +import okio.ByteString.Companion.encodeUtf8 +import okio.buffer +import org.junit.Assert.assertEquals +import org.junit.Test +import java.nio.charset.Charset + +class LimitedSinkTest { + + @Test(expected = IllegalArgumentException::class) + fun `should throw exception when limit is 0`() { + LimitedSink(Buffer(), 0L) + } + + @Test(expected = IllegalArgumentException::class) + fun `should throw exception when limit is negative`() { + LimitedSink(Buffer(), -1L) + } + + @Test + fun `should write to buffer only limited amount of bytes`() { + // given + val limited = Buffer() + val source = LimitedSink(limited, 10L).buffer() + + // when + source.write("0123456789012345678901234567890123456789".encodeUtf8()) // 40B + source.flush() + + // then + assertEquals("0123456789", limited.readString(Charset.forName("UTF-8"))) + } + + @Test + fun `should write to buffer only limited amount of bytes when written in parts`() { + // given + val limited = Buffer() + val source = LimitedSink(limited, 10L).buffer() + + // when + source.write("01234".encodeUtf8()) // 5B + source.flush() + source.write("5678901234".encodeUtf8()) // 10B + source.flush() + source.write("5678901234567890123456789".encodeUtf8()) // 25B + source.flush() + + // then + assertEquals("0123456789", limited.readString(Charset.forName("UTF-8"))) + } + + @Test + fun `should write all bytes when limit is bigger than input`() { + // given + val limited = Buffer() + val source = LimitedSink(limited, 10L).buffer() + + // when + source.write("01234".encodeUtf8()) // 5B + source.flush() + + // then + assertEquals("01234", limited.readString(Charset.forName("UTF-8"))) + } +} \ No newline at end of file diff --git a/ok2curl/src/test/java/com/moczul/ok2curl/OptionsTest.java b/ok2curl/src/test/java/com/moczul/ok2curl/OptionsTest.java deleted file mode 100644 index c9fae6c..0000000 --- a/ok2curl/src/test/java/com/moczul/ok2curl/OptionsTest.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.moczul.ok2curl; - -import org.junit.Test; - -import static org.hamcrest.CoreMatchers.hasItem; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; - -public class OptionsTest { - - @Test - public void defaultBuilderShouldReturnEmptyOptions() { - final Options options = Options.builder().build(); - - assertThat(options.list().size(), is(0)); - } - - @Test - public void shouldCorrectlyHandleOptionsParameters() { - final Options options = Options - .builder() - .maxTime(120) - .connectTimeout(60) - .retry(3) - .build(); - - assertThat(options.list().size(), is(3)); - assertThat(options.list(), hasItem("--max-time 120")); - assertThat(options.list(), hasItem("--connect-timeout 60")); - assertThat(options.list(), hasItem("--retry 3")); - } - - @Test - public void shouldReturnCorrectListOfParameters() { - final Options options = Options.builder() - .insecure() - .compressed() - .location() - .build(); - - assertThat(options.list().size(), is(3)); - assertThat(options.list(), hasItem("--insecure")); - assertThat(options.list(), hasItem("--compressed")); - assertThat(options.list(), hasItem("--location")); - } - - @Test - public void shouldIgnoreDuplicatedParameters() { - final Options options = Options.builder() - .insecure() - .insecure() - .insecure() - .build(); - - assertThat(options.list().size(), is(1)); - assertThat(options.list(), hasItem("--insecure")); - } -} diff --git a/ok2curl/src/test/java/com/moczul/ok2curl/util/FakeLogger.java b/ok2curl/src/test/java/com/moczul/ok2curl/util/FakeLogger.java deleted file mode 100644 index 18d46e6..0000000 --- a/ok2curl/src/test/java/com/moczul/ok2curl/util/FakeLogger.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.moczul.ok2curl.util; - -import com.moczul.ok2curl.logger.Loggable; - -public class FakeLogger implements Loggable { - - @Override - public void log(String message) { - System.out.println(message); - } -} diff --git a/ok2curl/src/test/java/com/moczul/ok2curl/util/HeaderModifierTest.java b/ok2curl/src/test/java/com/moczul/ok2curl/util/HeaderModifierTest.java deleted file mode 100644 index 7e09977..0000000 --- a/ok2curl/src/test/java/com/moczul/ok2curl/util/HeaderModifierTest.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.moczul.ok2curl.util; - -import com.moczul.ok2curl.CurlBuilder; -import com.moczul.ok2curl.Header; -import com.moczul.ok2curl.Options; -import com.moczul.ok2curl.modifier.HeaderModifier; - -import org.junit.Test; - -import java.util.Collections; -import java.util.List; - -import okhttp3.Request; - -import static junit.framework.Assert.assertEquals; - -public class HeaderModifierTest { - - private final HeaderModifier cookieHeaderModifier = new HeaderModifier() { - - @Override - public boolean matches(Header header) { - return "Cookie".equals(header.name()); - } - - @Override - public Header modify(Header header) { - return new Header(header.name(), "modifiedCookieValue"); - } - }; - - private final HeaderModifier nullHeaderModifier = new HeaderModifier() { - - @Override - public boolean matches(Header header) { - return true; - } - - @Override - public Header modify(Header header) { - return null; - } - }; - - @Test - public void curlCommand_shouldContains_modifiedHeader() { - final Request request = new Request.Builder() - .url("http://example.com/") - .header("Cookie", "FIRST=foo") - .build(); - - final List modifiers = Collections.singletonList(cookieHeaderModifier); - final String command = new CurlBuilder(request, -1L, modifiers, Options.EMPTY).build(); - - assertEquals("curl -X GET -H \"Cookie:modifiedCookieValue\" \"http://example.com/\"", command); - } - - @Test - public void curlCommand_shouldNotBeModified_ifDoesNotContainMatchingHeader() { - final Request request = new Request.Builder() - .url("http://example.com/") - .header("Accept", "application/json") - .build(); - final List modifiers = Collections.singletonList(cookieHeaderModifier); - - final String command = new CurlBuilder(request, -1L, modifiers, Options.EMPTY).build(); - - assertEquals("curl -X GET -H \"Accept:application/json\" \"http://example.com/\"", command); - } - - @Test - public void curlCommand_shouldNotContainsAnyHeaders_forNullHeaderModifier() { - final Request request = new Request.Builder() - .url("http://example.com/") - .header("Cookie", "FIRST=foo") - .header("Accept", "application/json") - .build(); - - final List modifiers = Collections.singletonList(nullHeaderModifier); - final String command = new CurlBuilder(request, -1L, modifiers, Options.EMPTY).build(); - - assertEquals(command, "curl -X GET \"http://example.com/\"", command); - } -} diff --git a/ok2curl/src/test/java/com/moczul/ok2curl/util/HeaderModifierTest.kt b/ok2curl/src/test/java/com/moczul/ok2curl/util/HeaderModifierTest.kt new file mode 100644 index 0000000..a1ed6a5 --- /dev/null +++ b/ok2curl/src/test/java/com/moczul/ok2curl/util/HeaderModifierTest.kt @@ -0,0 +1,89 @@ +package com.moczul.ok2curl.util + + +import com.moczul.ok2curl.Configuration +import com.moczul.ok2curl.CurlCommandGenerator +import com.moczul.ok2curl.Header +import com.moczul.ok2curl.modifier.HeaderModifier +import okhttp3.Request +import org.junit.Assert.assertEquals +import org.junit.Test + +class HeaderModifierTest { + private val cookieHeaderModifier: HeaderModifier = object : HeaderModifier { + override fun matches(header: Header): Boolean { + return "Cookie" == header.name + } + + override fun modify(header: Header): Header { + return Header(header.name, "modifiedCookieValue") + } + } + private val nullHeaderModifier: HeaderModifier = object : HeaderModifier { + override fun matches(header: Header): Boolean { + return true + } + + override fun modify(header: Header): Header? { + return null + } + } + + @Test + fun `should generate GET command with modified header`() { + // given + val config = Configuration(headerModifiers = listOf(cookieHeaderModifier)) + val curlGenerator = CurlCommandGenerator(configuration = config) + val request: Request = Request.Builder() + .url("http://example.com/") + .header("Cookie", "FIRST=foo") + .build() + + // when + val command = curlGenerator.generate(request) + + // then + assertEquals( + "curl -X GET -H \"Cookie:modifiedCookieValue\" \"http://example.com/\"", + command + ) + } + + @Test + fun `should generate GET command with unmodified header`() { + // given + val config = Configuration(headerModifiers = listOf(cookieHeaderModifier)) + val curlGenerator = CurlCommandGenerator(configuration = config) + val request: Request = Request.Builder() + .url("http://example.com/") + .header("Accept", "application/json") + .build() + + // when + val command = curlGenerator.generate(request) + + // then + assertEquals( + "curl -X GET -H \"Accept:application/json\" \"http://example.com/\"", + command + ) + } + + @Test + fun `should generate GET command without any headers`() { + // given + val config = Configuration(headerModifiers = listOf(nullHeaderModifier)) + val curlGenerator = CurlCommandGenerator(configuration = config) + val request: Request = Request.Builder() + .url("http://example.com/") + .header("Cookie", "FIRST=foo") + .header("Accept", "application/json") + .build() + + // when + val command = curlGenerator.generate(request) + + // then + assertEquals(command, "curl -X GET \"http://example.com/\"", command) + } +} \ No newline at end of file diff --git a/sample/build.gradle b/sample/build.gradle index 1ec253c..2e37db0 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -1,11 +1,12 @@ apply plugin: 'com.android.application' +apply plugin: 'org.jetbrains.kotlin.android' android { - compileSdkVersion 31 + compileSdkVersion 32 defaultConfig { applicationId "com.moczul.sample" - minSdkVersion 14 - targetSdkVersion 31 + minSdkVersion 16 + targetSdkVersion 32 versionCode 1 versionName "1.0" } @@ -32,21 +33,25 @@ dependencies { implementation project(':ok2curl') /** - * implementation 'com.github.mrmike:ok2curl:0.7.0' + * implementation 'com.github.mrmike:ok2curl:0.8.0' */ /** * Ok2Curl is packed with the latest version of OkHttp. If you already have OkHttp declared in * your dependencies or want to use different version simply add exclude module. * - * implementation ('com.github.mrmike:ok2curl:0.7.0') { + * implementation ('com.github.mrmike:ok2curl:0.8.0') { * exclude module: 'okhttp' * } */ - implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'androidx.appcompat:appcompat:1.4.1' implementation 'com.squareup.okhttp3:okhttp:4.9.3' + implementation 'com.google.android.material:material:1.5.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + implementation 'androidx.navigation:navigation-fragment-ktx:2.4.2' + implementation 'androidx.navigation:navigation-ui-ktx:2.4.2' testImplementation 'junit:junit:4.13.2' - testImplementation 'org.mockito:mockito-core:2.23.0' + testImplementation 'org.mockito:mockito-inline:4.3.1' } diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 5496032..0fc1104 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -23,4 +23,4 @@ - + \ No newline at end of file diff --git a/sample/src/main/java/com/moczul/sample/AndroidLogger.java b/sample/src/main/java/com/moczul/sample/AndroidLogger.java deleted file mode 100644 index 7fd440f..0000000 --- a/sample/src/main/java/com/moczul/sample/AndroidLogger.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.moczul.sample; - -import android.util.Log; - -import com.moczul.ok2curl.logger.Loggable; - -public class AndroidLogger implements Loggable { - - public static final String TAG = "Ok2Curl"; - - @Override - public void log(String message) { - Log.v(TAG, message); - } -} diff --git a/sample/src/main/java/com/moczul/sample/AndroidLogger.kt b/sample/src/main/java/com/moczul/sample/AndroidLogger.kt new file mode 100644 index 0000000..adc773c --- /dev/null +++ b/sample/src/main/java/com/moczul/sample/AndroidLogger.kt @@ -0,0 +1,15 @@ +package com.moczul.sample + +import android.util.Log +import com.moczul.ok2curl.logger.Logger + +class AndroidLogger : Logger { + + override fun log(message: String) { + Log.v(TAG, message) + } + + companion object { + const val TAG = "Ok2Curl" + } +} \ No newline at end of file diff --git a/sample/src/main/java/com/moczul/sample/MainActivity.java b/sample/src/main/java/com/moczul/sample/MainActivity.java deleted file mode 100644 index 463c234..0000000 --- a/sample/src/main/java/com/moczul/sample/MainActivity.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.moczul.sample; - -import android.content.Intent; -import android.os.Bundle; -import androidx.appcompat.app.AppCompatActivity; -import android.view.View; -import android.widget.TextView; - -import com.moczul.ok2curl.CurlBuilder; -import com.moczul.ok2curl.Options; -import com.moczul.ok2curl.modifier.HeaderModifier; -import com.moczul.sample.modifier.Base64Decoder; -import com.moczul.sample.modifier.BasicAuthorizationHeaderModifier; - -import java.util.Collections; -import java.util.List; - -public class MainActivity extends AppCompatActivity implements View.OnClickListener { - - private TextView curlLog; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - - curlLog = findViewById(R.id.curl_log); - - findViewById(R.id.get_request).setOnClickListener(this); - findViewById(R.id.post_request).setOnClickListener(this); - findViewById(R.id.get_request_modified).setOnClickListener(this); - } - - private void sendRequest(String type) { - final Intent intent = new Intent(this, RequestService.class); - intent.putExtra(RequestService.REQUEST_TYPE, type); - - startService(intent); - } - - private void displayCurlLog(String type) { - final BasicAuthorizationHeaderModifier modifier = new BasicAuthorizationHeaderModifier(new Base64Decoder()); - final List modifiers = Collections.singletonList(modifier); - - final String curl = new CurlBuilder(RequestFactory.getRequest(type), -1L, modifiers, Options.EMPTY).build(); - curlLog.setText(curl); - } - - @Override - public void onClick(View v) { - switch (v.getId()) { - case R.id.get_request: - sendRequest(RequestFactory.TYPE_GET); - displayCurlLog(RequestFactory.TYPE_GET); - break; - case R.id.post_request: - sendRequest(RequestFactory.TYPE_POST); - displayCurlLog(RequestFactory.TYPE_POST); - break; - case R.id.get_request_modified: - sendRequest(RequestFactory.TYPE_GET_MODIFIED); - displayCurlLog(RequestFactory.TYPE_GET_MODIFIED); - break; - default: - throw new IllegalArgumentException("Invalid view id"); - } - } -} diff --git a/sample/src/main/java/com/moczul/sample/MainActivity.kt b/sample/src/main/java/com/moczul/sample/MainActivity.kt new file mode 100644 index 0000000..f7b3651 --- /dev/null +++ b/sample/src/main/java/com/moczul/sample/MainActivity.kt @@ -0,0 +1,55 @@ +package com.moczul.sample + +import android.content.Intent +import android.os.Bundle +import android.view.View +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import com.moczul.ok2curl.Configuration +import com.moczul.ok2curl.CurlCommandGenerator +import com.moczul.ok2curl.modifier.HeaderModifier +import com.moczul.sample.modifier.Base64Decoder +import com.moczul.sample.modifier.BasicAuthorizationHeaderModifier + +class MainActivity : AppCompatActivity() { + + private lateinit var curlLog: TextView + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + curlLog = findViewById(R.id.curl_log) + + findViewById(R.id.get_request).setOnClickListener { + performRequest(RequestFactory.TYPE_GET) + } + + findViewById(R.id.post_request).setOnClickListener { + performRequest(RequestFactory.TYPE_POST) + } + + findViewById(R.id.get_request_modified).setOnClickListener { + performRequest(RequestFactory.TYPE_GET_MODIFIED) + } + } + + private fun performRequest(type: String) { + sendRequest(type) + displayCurlLog(type) + } + + private fun sendRequest(type: String) { + val intent = Intent(this, RequestService::class.java) + intent.putExtra(RequestService.REQUEST_TYPE, type) + startService(intent) + } + + private fun displayCurlLog(type: String) { + val modifier = BasicAuthorizationHeaderModifier(Base64Decoder()) + val modifiers: List = listOf(modifier) + val commandGenerator = CurlCommandGenerator(Configuration(modifiers)) + val curl: String = commandGenerator.generate(RequestFactory.getRequest(type)) + curlLog.text = curl + } + +} \ No newline at end of file diff --git a/sample/src/main/java/com/moczul/sample/RequestFactory.java b/sample/src/main/java/com/moczul/sample/RequestFactory.java deleted file mode 100644 index b6dfd77..0000000 --- a/sample/src/main/java/com/moczul/sample/RequestFactory.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.moczul.sample; - -import okhttp3.CacheControl; -import okhttp3.MediaType; -import okhttp3.Request; -import okhttp3.RequestBody; - -public class RequestFactory { - - public static final String TYPE_GET = "type_get"; - public static final String TYPE_POST = "type_post"; - public static final String TYPE_GET_MODIFIED = "type_get_modified"; - - private static final String NEW_REPO_BODY = "{" + - " \"name\": \"Hello-World\"," + - " \"description\": \"This is your first repository\"," + - " \"homepage\": \"https://github.com\"," + - " \"private\": false," + - " \"has_issues\": true," + - " \"has_wiki\": true," + - " \"has_downloads\": true" + - "}"; - - public static Request sampleGetRequest() { - return new Request.Builder() - .url("https://api.github.com/repos/vmg/redcarpet/issues?state=closed") - .cacheControl(CacheControl.FORCE_CACHE) - .build(); - } - - public static Request samplePostRequest() { - return new Request.Builder() - .url("https://api.github.com/user/repos") - .post(RequestBody.create(MediaType.parse("application/json"), NEW_REPO_BODY)) - .build(); - } - - public static Request modifiedGetRequest() { - return new Request.Builder() - .url("https://api.github.com/repos/vmg/redcarpet/issues?state=closed") - .header("Authorization", "Basic bWFjaWVrOnRham5laGFzbG8xMjM=") - .build(); - } - - public static Request getRequest(String type) { - switch (type) { - case TYPE_GET: - return sampleGetRequest(); - case TYPE_POST: - return samplePostRequest(); - case TYPE_GET_MODIFIED: - return modifiedGetRequest(); - default: - throw new IllegalArgumentException("Invalid request type"); - } - } - -} diff --git a/sample/src/main/java/com/moczul/sample/RequestFactory.kt b/sample/src/main/java/com/moczul/sample/RequestFactory.kt new file mode 100644 index 0000000..b51769d --- /dev/null +++ b/sample/src/main/java/com/moczul/sample/RequestFactory.kt @@ -0,0 +1,54 @@ +package com.moczul.sample + +import okhttp3.CacheControl +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody + +object RequestFactory { + + const val TYPE_GET = "type_get" + const val TYPE_POST = "type_post" + const val TYPE_GET_MODIFIED = "type_get_modified" + + private const val NEW_REPO_BODY = "{" + + " \"name\": \"Hello-World\"," + + " \"description\": \"This is your first repository\"," + + " \"homepage\": \"https://github.com\"," + + " \"private\": false," + + " \"has_issues\": true," + + " \"has_wiki\": true," + + " \"has_downloads\": true" + + "}" + + private fun sampleGetRequest(): Request { + return Request.Builder() + .url("https://api.github.com/repos/vmg/redcarpet/issues?state=closed") + .cacheControl(CacheControl.FORCE_CACHE) + .build() + } + + private fun samplePostRequest(): Request { + return Request.Builder() + .url("https://api.github.com/user/repos") + .post(NEW_REPO_BODY.toRequestBody("application/json".toMediaType())) + .build() + } + + private fun modifiedGetRequest(): Request { + return Request.Builder() + .url("https://api.github.com/repos/vmg/redcarpet/issues?state=closed") + .header("Authorization", "Basic bWFjaWVrOnRham5laGFzbG8xMjM=") + .build() + } + + @JvmStatic + fun getRequest(type: String?): Request { + return when (type) { + TYPE_GET -> sampleGetRequest() + TYPE_POST -> samplePostRequest() + TYPE_GET_MODIFIED -> modifiedGetRequest() + else -> throw IllegalArgumentException("Invalid request type") + } + } +} \ No newline at end of file diff --git a/sample/src/main/java/com/moczul/sample/RequestService.java b/sample/src/main/java/com/moczul/sample/RequestService.java deleted file mode 100644 index 35ff8f7..0000000 --- a/sample/src/main/java/com/moczul/sample/RequestService.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.moczul.sample; - -import android.app.IntentService; -import android.content.Intent; - -import com.moczul.ok2curl.CurlInterceptor; -import com.moczul.ok2curl.modifier.HeaderModifier; -import com.moczul.sample.modifier.Base64Decoder; -import com.moczul.sample.modifier.BasicAuthorizationHeaderModifier; - -import java.io.IOException; -import java.util.Collections; -import java.util.List; - -import okhttp3.OkHttpClient; -import okhttp3.Request; - -public class RequestService extends IntentService { - - public static final String REQUEST_TYPE = "request_type"; - - public RequestService() { - super("RequestService"); - } - - @Override - protected void onHandleIntent(Intent intent) { - final BasicAuthorizationHeaderModifier modifier = new BasicAuthorizationHeaderModifier(new Base64Decoder()); - final List modifiers = Collections.singletonList(modifier); - - final CurlInterceptor curlInterceptor = new CurlInterceptor(new AndroidLogger(), modifiers); - - final OkHttpClient client = new OkHttpClient.Builder() - .addInterceptor(curlInterceptor) - .build(); - - final String requestType = intent.getStringExtra(REQUEST_TYPE); - final Request request = RequestFactory.getRequest(requestType); - - try { - // This call will be logged via logcat - client.newCall(request).execute(); - } catch (IOException e) { - e.printStackTrace(); - } - } - -} diff --git a/sample/src/main/java/com/moczul/sample/RequestService.kt b/sample/src/main/java/com/moczul/sample/RequestService.kt new file mode 100644 index 0000000..adcc2de --- /dev/null +++ b/sample/src/main/java/com/moczul/sample/RequestService.kt @@ -0,0 +1,36 @@ +package com.moczul.sample + +import android.app.IntentService +import android.content.Intent +import com.moczul.ok2curl.Configuration +import com.moczul.ok2curl.CurlInterceptor +import com.moczul.sample.modifier.Base64Decoder +import com.moczul.sample.modifier.BasicAuthorizationHeaderModifier +import okhttp3.OkHttpClient +import java.io.IOException + +class RequestService : IntentService("RequestService") { + + override fun onHandleIntent(intent: Intent?) { + val modifier = BasicAuthorizationHeaderModifier(Base64Decoder()) + val config = Configuration(headerModifiers = listOf(modifier)) + val curlInterceptor = CurlInterceptor(AndroidLogger(), config) + + val client = OkHttpClient.Builder() + .addInterceptor(curlInterceptor) + .build() + val requestType = intent?.getStringExtra(REQUEST_TYPE) + val request = RequestFactory.getRequest(requestType) + + try { + // This call will be logged via logcat + client.newCall(request).execute() + } catch (e: IOException) { + e.printStackTrace() + } + } + + companion object { + const val REQUEST_TYPE = "request_type" + } +} \ No newline at end of file diff --git a/sample/src/main/java/com/moczul/sample/modifier/Base64Decoder.java b/sample/src/main/java/com/moczul/sample/modifier/Base64Decoder.java deleted file mode 100644 index 2c5f154..0000000 --- a/sample/src/main/java/com/moczul/sample/modifier/Base64Decoder.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.moczul.sample.modifier; - -import android.util.Base64; - -public class Base64Decoder { - - public String decode(String value) { - final byte[] decodedBytes = Base64.decode(value.getBytes(), Base64.DEFAULT); - return new String(decodedBytes); - } -} diff --git a/sample/src/main/java/com/moczul/sample/modifier/Base64Decoder.kt b/sample/src/main/java/com/moczul/sample/modifier/Base64Decoder.kt new file mode 100644 index 0000000..3b11a59 --- /dev/null +++ b/sample/src/main/java/com/moczul/sample/modifier/Base64Decoder.kt @@ -0,0 +1,11 @@ +package com.moczul.sample.modifier + +import android.util.Base64 + +class Base64Decoder { + + fun decode(value: String): String { + val decodedBytes = Base64.decode(value.toByteArray(), Base64.DEFAULT) + return String(decodedBytes) + } +} \ No newline at end of file diff --git a/sample/src/main/java/com/moczul/sample/modifier/BasicAuthorizationHeaderModifier.java b/sample/src/main/java/com/moczul/sample/modifier/BasicAuthorizationHeaderModifier.java deleted file mode 100644 index 86a18da..0000000 --- a/sample/src/main/java/com/moczul/sample/modifier/BasicAuthorizationHeaderModifier.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.moczul.sample.modifier; - -import com.moczul.ok2curl.Header; -import com.moczul.ok2curl.modifier.HeaderModifier; - -public class BasicAuthorizationHeaderModifier implements HeaderModifier { - - private final Base64Decoder base64Decoder; - - public BasicAuthorizationHeaderModifier(Base64Decoder base64Decoder) { - this.base64Decoder = base64Decoder; - } - - @Override - public boolean matches(Header header) { - return "Authorization".equals(header.name()) - && header.value().startsWith("Basic"); - } - - @Override - public Header modify(Header header) { - final String valueToDecode = header.value().replace("Basic", "").trim(); - final String decodedHeaderValue = base64Decoder.decode(valueToDecode); - return new Header(header.name(), decodedHeaderValue); - } -} diff --git a/sample/src/main/java/com/moczul/sample/modifier/BasicAuthorizationHeaderModifier.kt b/sample/src/main/java/com/moczul/sample/modifier/BasicAuthorizationHeaderModifier.kt new file mode 100644 index 0000000..968e53c --- /dev/null +++ b/sample/src/main/java/com/moczul/sample/modifier/BasicAuthorizationHeaderModifier.kt @@ -0,0 +1,16 @@ +package com.moczul.sample.modifier + +import com.moczul.ok2curl.Header +import com.moczul.ok2curl.modifier.HeaderModifier + +class BasicAuthorizationHeaderModifier(private val base64Decoder: Base64Decoder) : HeaderModifier { + override fun matches(header: Header): Boolean { + return "Authorization" == header.name && header.value.startsWith("Basic") + } + + override fun modify(header: Header): Header { + val valueToDecode = header.value.replace("Basic", "").trim() + val decodedHeaderValue = base64Decoder.decode(valueToDecode) + return Header(name = header.name, value = decodedHeaderValue) + } +} \ No newline at end of file diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml index 6cb9953..737b75f 100644 --- a/sample/src/main/res/values/strings.xml +++ b/sample/src/main/res/values/strings.xml @@ -1,5 +1,5 @@ - Sample + Ok2Curl Sample Sample GET request Sample POST request Get request with modified header diff --git a/sample/src/test/java/com/moczul/sample/ExampleUnitTest.java b/sample/src/test/java/com/moczul/sample/ExampleUnitTest.java deleted file mode 100644 index 251832f..0000000 --- a/sample/src/test/java/com/moczul/sample/ExampleUnitTest.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.moczul.sample; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * To work on unit tests, switch the Test Artifact in the Build Variants view. - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() throws Exception { - assertEquals(4, 2 + 2); - } -} diff --git a/sample/src/test/java/com/moczul/sample/modifier/BasicAuthorizationHeaderModifierTest.java b/sample/src/test/java/com/moczul/sample/modifier/BasicAuthorizationHeaderModifierTest.java deleted file mode 100644 index c40b17d..0000000 --- a/sample/src/test/java/com/moczul/sample/modifier/BasicAuthorizationHeaderModifierTest.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.moczul.sample.modifier; - -import com.moczul.ok2curl.Header; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; - -import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertThat; -import static org.mockito.Mockito.when; - -@RunWith(MockitoJUnitRunner.class) -public class BasicAuthorizationHeaderModifierTest { - @Mock - Base64Decoder base64Decoder; - @InjectMocks - BasicAuthorizationHeaderModifier headerModifier; - - private final Header basicAuthHeader = new Header("Authorization", "Basic bWFjaWVrOnRham5laGFzbG8xMjM="); - - @Test - public void basicAuthorizationHeader_shouldMatch_modifier() throws Exception { - assertThat(headerModifier.matches(basicAuthHeader), is(true)); - } - - @Test - public void nonBasicAuthorizationHeader_shouldNotMatch_modifier() throws Exception { - final Header header = new Header("Authorization", "Non basic header value"); - - assertThat(headerModifier.matches(header), is(false)); - } - - @Test - public void modifier_shouldReturnHeader_withDecodedValue() throws Exception { - when(base64Decoder.decode("bWFjaWVrOnRham5laGFzbG8xMjM=")).thenReturn("maciej:tajnehaslo123"); - - final Header modifiedHeader = headerModifier.modify(basicAuthHeader); - - assertThat(modifiedHeader.name(), is("Authorization")); - assertThat(modifiedHeader.value(), is("maciej:tajnehaslo123")); - } -} diff --git a/sample/src/test/java/com/moczul/sample/modifier/BasicAuthorizationHeaderModifierTest.kt b/sample/src/test/java/com/moczul/sample/modifier/BasicAuthorizationHeaderModifierTest.kt new file mode 100644 index 0000000..ae5692e --- /dev/null +++ b/sample/src/test/java/com/moczul/sample/modifier/BasicAuthorizationHeaderModifierTest.kt @@ -0,0 +1,48 @@ +package com.moczul.sample.modifier + +import com.moczul.ok2curl.Header +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.InjectMocks +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.junit.MockitoJUnitRunner + +@RunWith(MockitoJUnitRunner::class) +class BasicAuthorizationHeaderModifierTest { + + @Mock + private lateinit var base64Decoder: Base64Decoder + + @InjectMocks + private lateinit var headerModifier: BasicAuthorizationHeaderModifier + + + @Test + fun basicAuthorizationHeader_shouldMatch_modifier() { + val header = Header("Authorization", "Basic bWFjaWVrOnRham5laGFzbG8xMjM=") + + assertTrue(headerModifier.matches(header)) + } + + @Test + fun nonBasicAuthorizationHeader_shouldNotMatch_modifier() { + val header = Header("Authorization", "Non basic header value") + + assertFalse(headerModifier.matches(header)) + } + + @Test + fun modifier_shouldReturnHeader_withDecodedValue() { + `when`(base64Decoder.decode("bWFjaWVrOnRham5laGFzbG8xMjM=")).thenReturn("maciej:tajnehaslo123") + val header = Header("Authorization", "Basic bWFjaWVrOnRham5laGFzbG8xMjM=") + + val modifiedHeader = headerModifier.modify(header) + + val expectedHeader = Header(name = "Authorization", value = "maciej:tajnehaslo123") + assertEquals(expectedHeader, modifiedHeader) + } +} \ No newline at end of file