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

fix(api): Allow null associations on non-required fields. #2440

Merged
merged 3 commits into from
May 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -56,24 +56,22 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;

/**
* Converts provided model or class type into a request container
* with automatically generated GraphQL documents that follow
* AppSync specifications.
* Converts provided model or class type into a request container with automatically generated GraphQL documents that
* follow AppSync specifications.
*/
public final class AppSyncGraphQLRequestFactory {
private static final int DEFAULT_QUERY_LIMIT = 1000;

// This class should not be instantiated
private AppSyncGraphQLRequestFactory() {}
private AppSyncGraphQLRequestFactory() {
}

/**
* Creates a {@link GraphQLRequest} that represents a query that expects a single value as a result.
* The request will be created with the correct document based on the model schema and
* variables based on given {@code objectId}.
*
* Creates a {@link GraphQLRequest} that represents a query that expects a single value as a result. The request
* will be created with the correct document based on the model schema and variables based on given
* {@code objectId}.
* @param modelClass the model class.
* @param objectId the model identifier.
* @param <R> the response type.
Expand All @@ -82,30 +80,29 @@ private AppSyncGraphQLRequestFactory() {}
* @throws IllegalStateException when the model schema does not contain the expected information.
*/
public static <R, T extends Model> GraphQLRequest<R> buildQuery(
Class<T> modelClass,
String objectId
Class<T> modelClass,
String objectId
) {
try {
return AppSyncGraphQLRequest.builder()
.modelClass(modelClass)
.operation(QueryType.GET)
.requestOptions(new ApiGraphQLRequestOptions())
.responseType(modelClass)
.variable("id", "ID!", objectId)
.build();
.modelClass(modelClass)
.operation(QueryType.GET)
.requestOptions(new ApiGraphQLRequestOptions())
.responseType(modelClass)
.variable("id", "ID!", objectId)
.build();
} catch (AmplifyException exception) {
throw new IllegalStateException(
"Could not generate a schema for the specified class",
exception
"Could not generate a schema for the specified class",
exception
);
}
}

/**
* Creates a {@link GraphQLRequest} that represents a query that expects multiple values as a result.
* The request will be created with the correct document based on the model schema and variables
* for filtering based on the given predicate.
*
* Creates a {@link GraphQLRequest} that represents a query that expects multiple values as a result. The request
* will be created with the correct document based on the model schema and variables for filtering based on the
* given predicate.
* @param modelClass the model class.
* @param predicate the model predicate.
* @param <R> the response type.
Expand All @@ -114,20 +111,19 @@ public static <R, T extends Model> GraphQLRequest<R> buildQuery(
* @throws IllegalStateException when the model schema does not contain the expected information.
*/
public static <R, T extends Model> GraphQLRequest<R> buildQuery(
Class<T> modelClass,
QueryPredicate predicate
Class<T> modelClass,
QueryPredicate predicate
) {
Type dataType = TypeMaker.getParameterizedType(PaginatedResult.class, modelClass);
return buildQuery(modelClass, predicate, DEFAULT_QUERY_LIMIT, dataType);
}

/**
* Creates a {@link GraphQLRequest} that represents a query that expects multiple values as a result
* within a certain range (i.e. paginated).
*
* The request will be created with the correct document based on the model schema and variables
* for filtering based on the given predicate and pagination.
*
* Creates a {@link GraphQLRequest} that represents a query that expects multiple values as a result within a
* certain range (i.e. paginated).
* <p>
* The request will be created with the correct document based on the model schema and variables for filtering based
* on the given predicate and pagination.
* @param modelClass the model class.
* @param predicate the predicate for filtering.
* @param limit the page size/limit.
Expand All @@ -136,27 +132,27 @@ public static <R, T extends Model> GraphQLRequest<R> buildQuery(
* @return a valid {@link GraphQLRequest} instance.
*/
public static <R, T extends Model> GraphQLRequest<R> buildPaginatedResultQuery(
Class<T> modelClass,
QueryPredicate predicate,
int limit
Class<T> modelClass,
QueryPredicate predicate,
int limit
) {
Type responseType = TypeMaker.getParameterizedType(PaginatedResult.class, modelClass);
return buildQuery(modelClass, predicate, limit, responseType);
}

static <R, T extends Model> GraphQLRequest<R> buildQuery(
Class<T> modelClass,
QueryPredicate predicate,
int limit,
Type responseType
Class<T> modelClass,
QueryPredicate predicate,
int limit,
Type responseType
) {
try {
String modelName = ModelSchema.fromModelClass(modelClass).getName();
AppSyncGraphQLRequest.Builder builder = AppSyncGraphQLRequest.builder()
.modelClass(modelClass)
.operation(QueryType.LIST)
.requestOptions(new ApiGraphQLRequestOptions())
.responseType(responseType);
.modelClass(modelClass)
.operation(QueryType.LIST)
.requestOptions(new ApiGraphQLRequestOptions())
.responseType(responseType);

if (!QueryPredicates.all().equals(predicate)) {
String filterType = "Model" + Casing.capitalizeFirst(modelName) + "FilterInput";
Expand All @@ -167,15 +163,14 @@ static <R, T extends Model> GraphQLRequest<R> buildQuery(
return builder.build();
} catch (AmplifyException exception) {
throw new IllegalStateException(
"Could not generate a schema for the specified class",
exception
"Could not generate a schema for the specified class",
exception
);
}
}

/**
* Creates a {@link GraphQLRequest} that represents a mutation of a given type.
*
* @param model the model instance.
* @param predicate the model predicate.
* @param type the mutation type.
Expand All @@ -185,23 +180,23 @@ static <R, T extends Model> GraphQLRequest<R> buildQuery(
* @throws IllegalStateException when the model schema does not contain the expected information.
*/
public static <R, T extends Model> GraphQLRequest<R> buildMutation(
T model,
QueryPredicate predicate,
MutationType type
T model,
QueryPredicate predicate,
MutationType type
) {
try {
Class<? extends Model> modelClass = model.getClass();
ModelSchema schema = ModelSchema.fromModelClass(modelClass);
String graphQlTypeName = schema.getName();

AppSyncGraphQLRequest.Builder builder = AppSyncGraphQLRequest.builder()
.operation(type)
.modelClass(modelClass)
.requestOptions(new ApiGraphQLRequestOptions())
.responseType(modelClass);
.operation(type)
.modelClass(modelClass)
.requestOptions(new ApiGraphQLRequestOptions())
.responseType(modelClass);

String inputType =
Casing.capitalize(type.toString()) +
Casing.capitalize(type.toString()) +
Casing.capitalizeFirst(graphQlTypeName) +
"Input!"; // CreateTodoInput

Expand All @@ -213,7 +208,7 @@ public static <R, T extends Model> GraphQLRequest<R> buildMutation(

if (!QueryPredicates.all().equals(predicate)) {
String conditionType =
"Model" +
"Model" +
Casing.capitalizeFirst(graphQlTypeName) +
"ConditionInput";
builder.variable("condition", conditionType, parsePredicate(predicate));
Expand All @@ -222,15 +217,14 @@ public static <R, T extends Model> GraphQLRequest<R> buildMutation(
return builder.build();
} catch (AmplifyException exception) {
throw new IllegalStateException(
"Could not generate a schema for the specified class",
exception
"Could not generate a schema for the specified class",
exception
);
}
}

/**
* Creates a {@link GraphQLRequest} that represents a subscription of a given type.
*
* @param modelClass the model type.
* @param subscriptionType the subscription type.
* @param <R> the response type.
Expand All @@ -239,20 +233,20 @@ public static <R, T extends Model> GraphQLRequest<R> buildMutation(
* @throws IllegalStateException when the model schema does not contain the expected information.
*/
public static <R, T extends Model> GraphQLRequest<R> buildSubscription(
Class<T> modelClass,
SubscriptionType subscriptionType
Class<T> modelClass,
SubscriptionType subscriptionType
) {
try {
return AppSyncGraphQLRequest.builder()
.modelClass(modelClass)
.operation(subscriptionType)
.requestOptions(new ApiGraphQLRequestOptions())
.responseType(modelClass)
.build();
.modelClass(modelClass)
.operation(subscriptionType)
.requestOptions(new ApiGraphQLRequestOptions())
.responseType(modelClass)
.build();
} catch (AmplifyException exception) {
throw new IllegalStateException(
"Failed to build GraphQLRequest",
exception
"Failed to build GraphQLRequest",
exception
);
}
}
Expand All @@ -262,8 +256,8 @@ private static Map<String, Object> parsePredicate(QueryPredicate queryPredicate)
QueryPredicateOperation<?> qpo = (QueryPredicateOperation<?>) queryPredicate;
QueryOperator<?> op = qpo.operator();
return Collections.singletonMap(
qpo.field(),
Collections.singletonMap(appSyncOpType(op.type()), appSyncOpValue(op))
qpo.field(),
Collections.singletonMap(appSyncOpType(op.type()), appSyncOpValue(op))
);
} else if (queryPredicate instanceof QueryPredicateGroup) {
QueryPredicateGroup qpg = (QueryPredicateGroup) queryPredicate;
Expand Down Expand Up @@ -316,7 +310,7 @@ private static String appSyncOpType(QueryOperator.Type type) {
default:
throw new IllegalStateException(
"Tried to parse an unsupported QueryOperator type. Check if a new QueryOperator.Type enum " +
"has been created which is not supported in the AppSyncGraphQLRequestFactory."
"has been created which is not supported in the AppSyncGraphQLRequestFactory."
);
}
}
Expand Down Expand Up @@ -347,13 +341,13 @@ private static Object appSyncOpValue(QueryOperator<?> qOp) {
default:
throw new IllegalStateException(
"Tried to parse an unsupported QueryOperator type. Check if a new QueryOperator.Type enum " +
"has been created which is not implemented yet."
"has been created which is not implemented yet."
);
}
}

private static Map<String, Object> getDeleteMutationInputMap(
@NonNull ModelSchema schema, @NonNull Model instance) throws AmplifyException {
@NonNull ModelSchema schema, @NonNull Model instance) throws AmplifyException {
final Map<String, Object> input = new HashMap<>();
for (String fieldName : schema.getPrimaryIndexFields()) {
input.put(fieldName, extractFieldValue(fieldName, instance, schema));
Expand All @@ -362,7 +356,7 @@ private static Map<String, Object> getDeleteMutationInputMap(
}

private static Map<String, Object> getMapOfFieldNameAndValues(
@NonNull ModelSchema schema, @NonNull Model instance) throws AmplifyException {
@NonNull ModelSchema schema, @NonNull Model instance) throws AmplifyException {
if (!instance.getClass().getSimpleName().equals(schema.getName())) {
throw new AmplifyException(
"The object provided is not an instance of " + schema.getName() + ".",
Expand All @@ -381,8 +375,10 @@ private static Map<String, Object> getMapOfFieldNameAndValues(
if (association == null) {
result.put(fieldName, fieldValue);
} else if (association.isOwner()) {
Model target = (Model) Objects.requireNonNull(fieldValue);
result.put(association.getTargetName(), target.getPrimaryKeyString());
if (fieldValue != null) {
Model target = (Model) fieldValue;
result.put(association.getTargetName(), target.getPrimaryKeyString());
}
}
// Ignore if field is associated, but is not a "belongsTo" relationship
}
Expand All @@ -405,16 +401,16 @@ private static Map<String, Object> getMapOfFieldNameAndValues(
}

private static Object extractFieldValue(String fieldName, Model instance, ModelSchema schema)
throws AmplifyException {
throws AmplifyException {
try {
Field privateField = instance.getClass().getDeclaredField(fieldName);
privateField.setAccessible(true);
return privateField.get(instance);
} catch (Exception exception) {
throw new AmplifyException(
"An invalid field was provided. " + fieldName + " is not present in " + schema.getName(),
exception,
"Check if this model schema is a correct representation of the fields in the provided Object");
"An invalid field was provided. " + fieldName + " is not present in " + schema.getName(),
exception,
"Check if this model schema is a correct representation of the fields in the provided Object");
}
}
}
Loading