Reputation: 633
The process of using a request body is described in the official API Declaration page as such:
@POST("users/new")
Call<User> createUser(@Body User user);
While there is no guide for creating the User object, I imagine it can look something like this:
public class User {
public String name;
public String group;
}
By extension, this would result in a request body like this:
{
"name": string,
"group": string
}
By default, these fields seem to be optional. What is the best way I can make them required?
Upvotes: 0
Views: 3564
Reputation: 21105
There are many ways of accomplishing such a behavior. You can:
POST
ed before you invoke a Retrofitted-service (user input forms, etc), and let it fail fast.If you really need to validate the request bodies before they are sent, you should go with the first option. If you want to make the validation fully centralized, you can implement a custom Retrofit converter to make pre-validation on fly. (The code below uses Java 8 and a little bit of Google Guava, Retrofit 2 and Gson, however it can be easily reworked for another components).
Consider these:
interface IService {
@POST("/")
Call<String> post(
@Body User user
);
}
final class User {
final String name;
final String group;
User(final String name, final String group) {
this.name = name;
this.group = group;
}
}
Now we can implement Retrofit-stuff. The following mockOkHttpClient
is a mock OkHttpClient
to consume any request and respond with HTTP 200 OK
and "OK"
.
private static final OkHttpClient mockOkHttpClient = new OkHttpClient.Builder()
.addInterceptor(chain -> new Response.Builder()
.request(chain.request())
.protocol(HTTP_1_0)
.code(HTTP_OK)
.body(ResponseBody.create(MediaType.parse("application/json"), "\"OK\""))
.build()
)
.build();
Now let's make a simple test:
final Iterable<Retrofit> retrofits = ImmutableList.of(
getAsIsRetrofit(),
getValidatedDomainObjectsRetrofit(),
getValidatedDataTransferObjectsRetrofit()
);
final User user = new User("user", "group");
for ( final Retrofit retrofit : retrofits ) {
final IService service = retrofit.create(IService.class);
final String message = service.post(user).execute().body();
System.out.println(message);
}
As you can see, there are three Retrofit instances that are instantiated with different configurations to demonstrate each of them.
The following Retrofit
instance does not care the validation itself. And this is another time I recommend you to go with: simply post what you get as is and let the server API implementation deal with it itself. Consider the API implementation to return nice responses like HTTP 400 Bad Request
, etc.
private static Retrofit getAsIsRetrofit() {
return new Retrofit.Builder()
.client(mockOkHttpClient)
.baseUrl("http://whatever")
.addConverterFactory(GsonConverterFactory.create())
.build();
}
The following Retrofit
instance validates the given User
object before it's converted to a Gson-friendly representation (depends on if you have domain objects to data transfer object transformations in your application):
private static Retrofit getValidatedDomainObjectsRetrofit() {
return new Retrofit.Builder()
.client(mockOkHttpClient)
.baseUrl("http://whatever")
.addConverterFactory(new Converter.Factory() {
@Override
public Converter<?, RequestBody> requestBodyConverter(final Type type, final Annotation[] parameterAnnotations,
final Annotation[] methodAnnotations, final Retrofit retrofit) {
if ( type != User.class ) {
return null;
}
final Converter<Object, RequestBody> nextConverter = retrofit.nextRequestBodyConverter(this, type, parameterAnnotations, methodAnnotations);
return (Converter<Object, RequestBody>) value -> {
if ( value instanceof User ) {
final User user = (User) value;
requireNonNull(user.name, "name must not be null");
requireNonNull(user.group, "group must not be null");
}
return nextConverter.convert(value);
};
}
})
.addConverterFactory(GsonConverterFactory.create())
.build();
}
And the next one validates data transfer objects before they are written to output streams. Probably the most low-level instance here.
private static Retrofit getValidatedDataTransferObjectsRetrofit() {
final Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new TypeAdapterFactory() {
@Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
if ( typeToken.getRawType() != User.class ) {
return null;
}
final TypeAdapter<T> delegateTypeAdapter = gson.getDelegateAdapter(this, typeToken);
return new TypeAdapter<T>() {
@Override
public void write(final JsonWriter out, final T value)
throws IOException {
if ( value instanceof User ) {
final User user = (User) value;
requireNonNull(user.name, "name must not be null");
requireNonNull(user.group, "group must not be null");
}
delegateTypeAdapter.write(out, value);
}
@Override
public T read(final JsonReader in)
throws IOException {
return delegateTypeAdapter.read(in);
}
};
}
})
.create();
return new Retrofit.Builder()
.client(mockOkHttpClient)
.baseUrl("http://whatever")
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
}
Note that requireNonNull
is a JDK 8 method, and if you want something like @NotNull
, you can implement your own annotation processor, or find such an implementation in the Internet considering my implementation idea useless. :) However, I think you'd like the as-is approach the most.
Upvotes: 3