Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[non-gen service] EventService #760

Open
wants to merge 4 commits into
base: autogen/service
from
Open
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.

Always

Just for now

@@ -1,16 +1,5 @@
package com.stripe.net;

import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;

import java.io.IOException;
import java.util.Map;

/**
@@ -23,70 +12,34 @@
/**
* Interface implemented by all enum parameter to get the actual string value that Stripe API
* expects. Internally, it used in custom serialization
* {@link ApiRequestParams.HasEmptyEnumTypeAdapterFactory} converting empty string enum to null.
* {@link ApiRequestParamsConverter} converting empty string enum to
* null.
*/
public interface EnumParam {
String getValue();
}

private static final Gson GSON = new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.registerTypeAdapterFactory(new HasEmptyEnumTypeAdapterFactory())
.create();

private static final UntypedMapDeserializer UNTYPED_MAP_DESERIALIZER =
new UntypedMapDeserializer();

private static class HasEmptyEnumTypeAdapterFactory implements TypeAdapterFactory {
@SuppressWarnings("unchecked")
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (!EnumParam.class.isAssignableFrom(type.getRawType())) {
return null;
}

TypeAdapter<EnumParam> paramEnum = new TypeAdapter<EnumParam>() {
@Override
public void write(JsonWriter out, EnumParam value) throws IOException {
if (value.getValue().equals("")) {
// need to restore serialize null setting
// not to affect other fields
boolean previousSetting = out.getSerializeNulls();
out.setSerializeNulls(true);
out.nullValue();
out.setSerializeNulls(previousSetting);
} else {
out.value(value.getValue());
}
}
/**
* Param key for an `extraParams` map. Any param/sub-param specifying a field
* intended to support extra params from users should have the annotation
* {@code @SerializedName(ApiRequestParams.EXTRA_PARAMS_KEY)}. Logic to handle this is in
* {@link ApiRequestParamsConverter}.
*/
public static final String EXTRA_PARAMS_KEY = "_stripe_java_extra_param_key";

@Override
public EnumParam read(JsonReader in) {
throw new UnsupportedOperationException(
"No deserialization is expected from this private type adapter for enum param.");
}
};
return (TypeAdapter<T>) paramEnum.nullSafe();
}
}
/**
* Converter mapping typed API request parameters into an untyped map.
*/
private static final ApiRequestParamsConverter PARAMS_CONVERTER =
new ApiRequestParamsConverter();

/**
* Convenient method to convert this typed request params into an untyped map. This map is
* composed of {@code Map<String, Object>}, {@code List<Object>}, and basic Java data types.
* This allows you to test building the request params and verify compatibility with your
* prior integrations using the untyped params map
* {@link ApiResource#request(ApiResource.RequestMethod, String, Map, Class, RequestOptions)}.
*
* <p>The peculiarity of this conversion is that `EMPTY` {@link EnumParam} with raw
* value of empty string will be converted to null. This is compatible with the existing
* contract enforcing no empty string in the untyped map params.
*
* <p>Because of the translation from `EMPTY` enum to null, deserializing this map back to a
* request instance is lossy. The null value will not be converted back to the `EMPTY` enum.
* Convert `this` api request params to an untyped map. The conversion is specific to api
* request params object. Please see documentation in
* {@link ApiRequestParamsConverter#convert(ApiRequestParams)}.
*/
public Map<String, Object> toMap() {
JsonObject json = GSON.toJsonTree(this).getAsJsonObject();
return UNTYPED_MAP_DESERIALIZER.deserialize(json);
return PARAMS_CONVERTER.convert(this);
}
}

@@ -0,0 +1,128 @@
package com.stripe.net;

import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;

import com.stripe.Stripe;

import com.stripe.param.common.EmptyParam;
import java.io.IOException;
import java.util.Map;

/**
* Converter to map an api request object to an untyped map.
* It is not called a *Serializer because the outcome is not a JSON data.
* It is not called *UntypedMapDeserializer because it is not converting from JSON.
*/
class ApiRequestParamsConverter {
/**
* Strategy to flatten extra params in the API request parameters.
*/
private static class ExtraParamsFlatteningStrategy implements UntypedMapDeserializer.Strategy {
@Override
public void deserializeAndTransform(Map<String, Object> outerMap,
Map.Entry<String, JsonElement> jsonEntry,
UntypedMapDeserializer untypedMapDeserializer) {
String key = jsonEntry.getKey();
JsonElement value = jsonEntry.getValue();
if (ApiRequestParams.EXTRA_PARAMS_KEY.equals(key)) {
if (!value.isJsonObject()) {
throw new IllegalStateException(String.format(
"Unexpected schema for extra params. JSON object is expected at key `%s`, but found"
+ " `%s`. This is likely a problem with this current library version `%s`. "
+ "Please contact support@stripe.com for assistance.",
ApiRequestParams.EXTRA_PARAMS_KEY, value, Stripe.VERSION));
}
// JSON value now corresponds to the extra params map, and is also deserialized as a map.
// Instead of putting this result map under the original key, flatten the map
// by adding all its key/value pairs to the outer map instead.
Map<String, Object> extraParamsMap =
untypedMapDeserializer.deserialize(value.getAsJsonObject());
outerMap.putAll(extraParamsMap);
} else {
// Normal deserialization where output map has the same structure as the given JSON content.
// The deserialized content is an untyped `Object` and added to the outer map at the
// original key.
outerMap.put(key, untypedMapDeserializer.deserializeJsonElement(value));
}
}
}

/**
* Type adapter to convert an empty enum to null value to comply with the lower-lever encoding
* logic for the API request parameters.
*/
private static class HasEmptyEnumTypeAdapterFactory implements TypeAdapterFactory {
@SuppressWarnings("unchecked")
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (!ApiRequestParams.EnumParam.class.isAssignableFrom(type.getRawType())) {
return null;
}

TypeAdapter<ApiRequestParams.EnumParam> paramEnum =
new TypeAdapter<ApiRequestParams.EnumParam>() {
@Override
public void write(JsonWriter out, ApiRequestParams.EnumParam value) throws IOException {
if (value.getValue().equals("")) {
// need to restore serialize null setting
// not to affect other fields
boolean previousSetting = out.getSerializeNulls();
out.setSerializeNulls(true);
out.nullValue();
out.setSerializeNulls(previousSetting);
} else {
out.value(value.getValue());
}
}

@Override
public ApiRequestParams.EnumParam read(JsonReader in) {
throw new UnsupportedOperationException(
"No deserialization is expected from this private type adapter for enum param.");
}
};
return (TypeAdapter<T>) paramEnum.nullSafe();
}
}

private static final Gson GSON = new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.registerTypeAdapterFactory(new ApiRequestParamsConverter.HasEmptyEnumTypeAdapterFactory())
.create();

private static final UntypedMapDeserializer FLATTENING_EXTRA_PARAMS_DESERIALIZER =
new UntypedMapDeserializer(new ExtraParamsFlatteningStrategy());

/**
* Convert the given request params into an untyped map. This map is
* composed of {@code Map<String, Object>}, {@code List<Object>}, and basic Java data types.
* This allows you to test building the request params and verify compatibility with your
* prior integrations using the untyped params map
* {@link ApiResource#request(ApiResource.RequestMethod, String, Map, Class, RequestOptions)}.
*
* <p>There are two peculiarities in this conversion:
*
* <p>1) {@link EmptyParam#EMPTY}, containing a raw empty string value, is converted to null.
* This is because the form-encoding layer prohibits passing empty string as a param map value.
* It, however, allows a null value in the map (present key but null value).
* Because of the translation from `EMPTY` enum to null, deserializing this map back to a
* request instance is lossy. The null value will not be converted back to the `EMPTY` enum.
*
* <p>2) Parameter with serialized name {@link ApiRequestParams#EXTRA_PARAMS_KEY} will be
* flattened. This is to support passing new params that the current library has not
* yet supported.
*/
Map<String, Object> convert(ApiRequestParams apiRequestParams) {
JsonObject jsonParams = GSON.toJsonTree(apiRequestParams).getAsJsonObject();
return FLATTENING_EXTRA_PARAMS_DESERIALIZER.deserialize(jsonParams);
}
}
@@ -0,0 +1,99 @@
package com.stripe.net;

import com.stripe.Stripe;
import com.stripe.exception.InvalidRequestException;
import com.stripe.exception.StripeException;
import com.stripe.model.StripeCollectionInterface;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.MissingFormatArgumentException;
import java.util.Optional;

/**
* Super class to services that make API calls. The service sub-class takes in
* {@link ApiRequestParams} and returns {@link ApiResource} or
* {@link com.stripe.model.StripeCollection} similarly to the methods on the
* resource itself.
*/
public abstract class ApiService {
/**
* Helper class to handle input null params, so users can still conveniently pass null instead of
* invoking an empty builder.
*/
private static class ApiServiceNullParams extends ApiRequestParams {
}

/**
* Build a resource url given the url format (containing `%s`) and url variables.
* The url variables will be UTF-8 encoded.
* @param urlFormat standard string format to be used with {@link String#format}
* @param urlVariables un-encoded url variables as arguments to the url format.
* @return url for the resource
*/
protected String resourceUrl(String urlFormat, String... urlVariables)
throws InvalidRequestException {
List<String> urlVariableList = new ArrayList<>();
for (String variable : Arrays.asList(urlVariables)) {
urlVariableList.add(ApiResource.urlEncodeId(variable));
}

String format;
try {
format = String.format(urlFormat, urlVariableList.toArray());
} catch (MissingFormatArgumentException ex) {
throw new InvalidRequestException(String.format(
"Unable to create url with pattern `%s` with url variables `%s`. "
+ "This is likely a problem with this current library version `%s`. "
+ "Please contact support@stripe.com for assistance.",
urlFormat, urlVariableList, Stripe.VERSION),
null, null, null, 0, ex);
}

return String.format("%s%s", Stripe.getApiBase(), format);
}

/**
* Make an api request with a valid url like that from {@code resourceUrl}. This method is more
* general than its counterpart {@code requestCollection} to list collection classes.
* This implementation uses the same underlying method as that in resource class like
* {@code Charge.create(params, opts)}.
* @param method http method
* @param url valid url
* @param params nullable request params; null value will be converted to empty params
* @param clazz class to which JSON response is deserialized to
* @param options request options
* @return object of type clazz
* @throws StripeException exceptions containing error code and information helpful for debugging
* and reporting to stripe support.
*/
protected static <T> T request(ApiResource.RequestMethod method,
String url, ApiRequestParams params, Class<T> clazz,
RequestOptions options) throws StripeException {
return ApiResource.request(method, url, nullSafeParams(params), clazz, options);
}

/**
* Make an api request for collection classes with a valid url like that from {@code resourceUrl}.
* This implementation uses the same underlying method as that in resource class when obtaining
* object collection like {@code Charge.list(params)} to get {@code ChargeCollection}. Note
* that http method cannot be specified; the underlying implementation defaults to GET.
* @param url a valid url
* @param params nullable request params; null value will be converted to empty params
* @param clazz stripe collection class to which JSON response is deserialized to
* @param options request options
* @return stripe collection object
* @throws StripeException exceptions containing error code and information helpful for debugging
* and reporting to stripe support.
*/
protected static <T extends StripeCollectionInterface<?>> T requestCollection(
String url, ApiRequestParams params, Class<T> clazz, RequestOptions options)
throws StripeException {
return ApiResource.requestCollection(url, nullSafeParams(params), clazz, options);
}

private static ApiRequestParams nullSafeParams(ApiRequestParams params) {
return Optional.ofNullable(params).orElse(new ApiServiceNullParams());
}
}
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.