chore: Add separate request/response views (#31640)

This PR gets finer control into what fields are allowed in
request-body-only, vs what's allowed in response-body-only. This leaves
the fields to separately controlled regarding what can go into the
database and what can't.

[Slack
thread](https://theappsmith.slack.com/archives/CPQNLFHTN/p1710125307810949).
This commit is contained in:
Shrikant Sharat Kandula 2024-03-13 14:50:03 +05:30 committed by GitHub
parent 863214785a
commit 4cdbe89586
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 28 additions and 30 deletions

View File

@ -16,9 +16,9 @@ import com.appsmith.external.models.Executable;
import com.appsmith.external.models.PluginType; import com.appsmith.external.models.PluginType;
import com.appsmith.external.models.Policy; import com.appsmith.external.models.Policy;
import com.appsmith.external.models.Property; import com.appsmith.external.models.Property;
import com.appsmith.external.views.ResponseOnly;
import com.appsmith.external.views.Views; import com.appsmith.external.views.Views;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonView; import com.fasterxml.jackson.annotation.JsonView;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
@ -88,9 +88,8 @@ public class ActionCE_DTO implements Identifiable, Executable {
ActionConfiguration actionConfiguration; ActionConfiguration actionConfiguration;
// this attribute carries error messages while processing the actionCollection // this attribute carries error messages while processing the actionCollection
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@Transient @Transient
@JsonView(Views.Public.class) @JsonView(ResponseOnly.class)
List<ErrorDTO> errorReports; List<ErrorDTO> errorReports;
@JsonView(Views.Public.class) @JsonView(Views.Public.class)
@ -106,23 +105,19 @@ public class ActionCE_DTO implements Identifiable, Executable {
@JsonView(Views.Public.class) @JsonView(Views.Public.class)
List<Property> dynamicBindingPathList; List<Property> dynamicBindingPathList;
@JsonProperty(access = JsonProperty.Access.READ_ONLY) @JsonView(ResponseOnly.class)
@JsonView(Views.Public.class)
Boolean isValid; Boolean isValid;
@JsonProperty(access = JsonProperty.Access.READ_ONLY) @JsonView(ResponseOnly.class)
@JsonView(Views.Public.class)
Set<String> invalids; Set<String> invalids;
@Transient @Transient
@JsonProperty(access = JsonProperty.Access.READ_ONLY) @JsonView(ResponseOnly.class)
@JsonView(Views.Public.class)
Set<String> messages = new HashSet<>(); Set<String> messages = new HashSet<>();
// This is a list of keys that the client whose values the client needs to send during action execution. // This is a list of keys that the client whose values the client needs to send during action execution.
// These are the Mustache keys that the server will replace before invoking the API // These are the Mustache keys that the server will replace before invoking the API
@JsonProperty(access = JsonProperty.Access.READ_ONLY) @JsonView(ResponseOnly.class)
@JsonView(Views.Public.class)
Set<String> jsonPathKeys; Set<String> jsonPathKeys;
@JsonView(Views.Internal.class) @JsonView(Views.Internal.class)

View File

@ -0,0 +1,7 @@
package com.appsmith.external.views;
/**
* Intended to annotate fields that can be set by HTTP request payloads, but should NOT be included
* in HTTP responses sent back to the client.
*/
public interface RequestOnly extends Views.Public {}

View File

@ -0,0 +1,8 @@
package com.appsmith.external.views;
/**
* Intended to mark entity/DTO fields that should be included as part of HTTP responses, but should
* be ignored as part of HTTP requests. For example, if a field is marked with this annotation, in
* a class used with {@code @RequestBody}, it's value will NOT be deserialized.
*/
public interface ResponseOnly extends Views.Public {}

View File

@ -6,23 +6,19 @@ import com.appsmith.server.newactions.base.NewActionService;
import com.appsmith.server.refactors.applications.RefactoringService; import com.appsmith.server.refactors.applications.RefactoringService;
import com.appsmith.server.services.LayoutActionService; import com.appsmith.server.services.LayoutActionService;
import com.appsmith.server.solutions.ActionExecutionSolution; import com.appsmith.server.solutions.ActionExecutionSolution;
import io.micrometer.observation.ObservationRegistry;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
@RequestMapping(Url.ACTION_URL) @RequestMapping(Url.ACTION_URL)
@Slf4j
public class ActionController extends ActionControllerCE { public class ActionController extends ActionControllerCE {
public ActionController( public ActionController(
LayoutActionService layoutActionService, LayoutActionService layoutActionService,
NewActionService newActionService, NewActionService newActionService,
RefactoringService refactoringService, RefactoringService refactoringService,
ActionExecutionSolution actionExecutionSolution, ActionExecutionSolution actionExecutionSolution) {
ObservationRegistry observationRegistry) {
super(layoutActionService, newActionService, refactoringService, actionExecutionSolution, observationRegistry); super(layoutActionService, newActionService, refactoringService, actionExecutionSolution);
} }
} }

View File

@ -2,6 +2,7 @@ package com.appsmith.server.controllers.ce;
import com.appsmith.external.models.ActionDTO; import com.appsmith.external.models.ActionDTO;
import com.appsmith.external.models.ActionExecutionResult; import com.appsmith.external.models.ActionExecutionResult;
import com.appsmith.external.views.RequestOnly;
import com.appsmith.external.views.Views; import com.appsmith.external.views.Views;
import com.appsmith.server.constants.FieldName; import com.appsmith.server.constants.FieldName;
import com.appsmith.server.constants.Url; import com.appsmith.server.constants.Url;
@ -16,7 +17,6 @@ import com.appsmith.server.refactors.applications.RefactoringService;
import com.appsmith.server.services.LayoutActionService; import com.appsmith.server.services.LayoutActionService;
import com.appsmith.server.solutions.ActionExecutionSolution; import com.appsmith.server.solutions.ActionExecutionSolution;
import com.fasterxml.jackson.annotation.JsonView; import com.fasterxml.jackson.annotation.JsonView;
import io.micrometer.observation.ObservationRegistry;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -48,30 +48,25 @@ public class ActionControllerCE {
private final NewActionService newActionService; private final NewActionService newActionService;
private final RefactoringService refactoringService; private final RefactoringService refactoringService;
private final ActionExecutionSolution actionExecutionSolution; private final ActionExecutionSolution actionExecutionSolution;
private final ObservationRegistry observationRegistry;
@Autowired @Autowired
public ActionControllerCE( public ActionControllerCE(
LayoutActionService layoutActionService, LayoutActionService layoutActionService,
NewActionService newActionService, NewActionService newActionService,
RefactoringService refactoringService, RefactoringService refactoringService,
ActionExecutionSolution actionExecutionSolution, ActionExecutionSolution actionExecutionSolution) {
ObservationRegistry observationRegistry) {
this.layoutActionService = layoutActionService; this.layoutActionService = layoutActionService;
this.newActionService = newActionService; this.newActionService = newActionService;
this.refactoringService = refactoringService; this.refactoringService = refactoringService;
this.actionExecutionSolution = actionExecutionSolution; this.actionExecutionSolution = actionExecutionSolution;
this.observationRegistry = observationRegistry;
} }
@JsonView(Views.Public.class) @JsonView(Views.Public.class)
@PostMapping @PostMapping
@ResponseStatus(HttpStatus.CREATED) @ResponseStatus(HttpStatus.CREATED)
public Mono<ResponseDTO<ActionDTO>> createAction( public Mono<ResponseDTO<ActionDTO>> createAction(
@Valid @RequestBody ActionDTO resource, @Valid @RequestBody @JsonView(RequestOnly.class) ActionDTO resource,
@RequestHeader(name = FieldName.BRANCH_NAME, required = false) String branchName, @RequestHeader(name = FieldName.BRANCH_NAME, required = false) String branchName) {
@RequestHeader(name = "Origin", required = false) String originHeader,
ServerWebExchange exchange) {
log.debug("Going to create resource {}", resource.getClass().getName()); log.debug("Going to create resource {}", resource.getClass().getName());
return layoutActionService return layoutActionService
.createSingleActionWithBranch(resource, branchName) .createSingleActionWithBranch(resource, branchName)
@ -82,7 +77,7 @@ public class ActionControllerCE {
@PutMapping("/{defaultActionId}") @PutMapping("/{defaultActionId}")
public Mono<ResponseDTO<ActionDTO>> updateAction( public Mono<ResponseDTO<ActionDTO>> updateAction(
@PathVariable String defaultActionId, @PathVariable String defaultActionId,
@Valid @RequestBody ActionDTO resource, @Valid @RequestBody @JsonView(RequestOnly.class) ActionDTO resource,
@RequestHeader(name = FieldName.BRANCH_NAME, required = false) String branchName) { @RequestHeader(name = FieldName.BRANCH_NAME, required = false) String branchName) {
log.debug("Going to update resource with defaultActionId: {}, branch: {}", defaultActionId, branchName); log.debug("Going to update resource with defaultActionId: {}, branch: {}", defaultActionId, branchName);
return layoutActionService return layoutActionService
@ -178,9 +173,6 @@ public class ActionControllerCE {
* <p> * <p>
* The controller function is primarily used with param applicationId by the client to fetch the actions in edit * The controller function is primarily used with param applicationId by the client to fetch the actions in edit
* mode. * mode.
*
* @param params
* @return
*/ */
@JsonView(Views.Public.class) @JsonView(Views.Public.class)
@GetMapping("") @GetMapping("")