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

Add RequestBodySchema and APIResponseSchema annotations #410

Merged
merged 4 commits into from Apr 7, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -0,0 +1,79 @@
/**
* Copyright (c) 2020 Contributors to the Eclipse Foundation
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.
*/

package org.eclipse.microprofile.openapi.annotations.parameters;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Provides a reference to a class that (after introspection) describes the schema
* for a single request body. This annotation provides a short-hand way to specify a
* simple request body that would otherwise be specified using {@link RequestBody &#64;RequestBody}
* and that typically could not be determined by scanning the resource method alone.
*
* <p>
* The following annotation usages are equivalent to the OpenAPI annotation scanner runtime.
*
* <pre>
* &#64;RequestBody(content = { &#64;Content(schema = &#64;Schema(implementation = MyRequestObject.class)) })
*
* &#64;RequestBodySchema(MyRequestObject.class)
* </pre>
*
* <p>
* Any media types that apply to the resource method from either a method-level or class-level
* <code>&#64;Consumes</code> annotation will result in those media types applying to the OpenAPI
* request body model.
*
* <p>
* This annotation is useful in cases when a single request body schema applies to all media types (as
* given in <code>&#64;Consumes</code>), where it is not possible for class introspection to
* determine the schema directly.
*
* <pre>
* &#64;PUT
* &#64;Path("{id}")
* &#64;RequestBodySchema(MyRequestObject.class)
* public Response updateItem(&#64;PathParam("{id}") long id, InputStream rawData) {
* MyRequestObject entity = service.deserialize(rawData);
* service.persist(entity);
*
* return Response.status(204).build();
* }
* </pre>
*
* @see RequestBody
* @see <a href="https://github.com/OAI/OpenAPI-Specification/blob/3.0.0-rc2/versions/3.0.md#requestBodyObject">OpenAPI requestBody Object</a>
**/
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface RequestBodySchema {

/**
* Provides a Java class as implementation for this schema. The class will
* undergo introspection to determine any applicable Schema attributes to be
* applied to the OpenAPI request body model.
*
* @return a class that implements this schema
**/
Class<?> value();

}
Expand Up @@ -29,7 +29,7 @@
import org.eclipse.microprofile.openapi.annotations.media.Content;

/**
* The ApiResponse annotation corresponds to the OpenAPI Response model object which
* The APIResponse annotation corresponds to the OpenAPI Response model object which
* describes a single response from an API Operation, including design-time,
* static links to operations based on the response.
* <p>
Expand All @@ -38,7 +38,7 @@
* response with the specified responseCode the annotation on the method is ignored.
*
* <pre>
* &#64;ApiResponse(responseCode="200", description="Calculate load size", content=
* &#64;APIResponse(responseCode="200", description="Calculate load size", content=
* { &#64;Content(mediaType="application/json", Schema=&#64;Schema(type="integer")) } )
* &#64;GET
* public getLuggageWeight(Flight id) {
Expand Down
@@ -0,0 +1,110 @@
/**
* Copyright (c) 2020 Contributors to the Eclipse Foundation
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.
*/
package org.eclipse.microprofile.openapi.annotations.responses;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* The APIResponseSchema annotation corresponds to an individual schema in the OpenAPI
* Response model object which describes a single response from an API Operation. This
* annotation provides a short-hand way to specify a simple response that would otherwise
* be specified using {@link APIResponse &#64;APIResponse} and that typically could not be determined by
* scanning the resource method alone.
*
* <p>
* The following annotation usages are equivalent to the OpenAPI annotation scanner runtime.
*
* <pre>
* &#64;APIResponse(content = { &#64;Content(schema = &#64;Schema(implementation = MyResponseObject.class)) })
*
* &#64;APIResponseSchema(MyResponseObject.class)
* </pre>
*
* <p>
* When this annotation is applied to a method the response is added to the responses
* defined in the corresponding OpenAPI operation with a default response code and description
* that correspond to the method's HTTP method annotation and return type. Any media types that
* apply to the resource method from either a method-level or class-level <code>&#64;Produces</code>
* annotation will result in those media types applying to the OpenAPI response model.
*
* <p>
* If not specified, default responseCode and responseDescription values shall be determined
* according to the {@link #responseCode() responseCode} and {@link #responseDescription() responseDescription}
* documentation.
*
* <pre>
* &#64;GET
* &#64;Path("{id}")
* &#64;APIResponseSchema(value = MyResponseObject.class, responseDescription = "Success", responseCode = "200")
* public Response getById(&#64;PathParam("{id}") long id) {
* MyResponseObject entity = service.load(id);
* return Response.status(200).entity(entity).build();
* }
* </pre>
*
* @since 2.0
* @see APIResponse
* @see "https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#responseObject"
*
**/
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface APIResponseSchema {

/**
* Provides a Java class as implementation for this schema. The class will
* undergo introspection to determine any applicable Schema attributes to be
* applied to the OpenAPI response model.
*
* @return a class that implements this schema
**/
Class<?> value();

/**
* A short description of the response. It is a REQUIRED property in the OpenAPI document.
*
* <p>
* If no value is specified, the default value will set to the description given by the
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10">HTTP/1.1 documentation</a>
* for the {@link #responseCode() responseCode} in use.
*
* @return description of the response.
**/
String responseDescription() default "";

/**
* The HTTP response code, or 'default', for the supplied response. May have only one 'default' entry.
*
* <p>
* If no value is specified, a default shall be determined using REST conventions as follows:
*
* <ul>
* <li>If the method's return type is <code>void</code> and the HTTP method is <code>&#64;POST</code>,
* the code will be <code>201</code>.
* <li>Otherwise, if the method's return type is <code>void</code> the method does not list a JAX-RS
* <code>AsyncResponse</code> parameter, the code will be <code>204</code>.
* <li>Otherwise, the code will be <code>200</code>.
* </ul>
*
* @return HTTP response code for this response instance or default
**/
String responseCode() default "";
}
2 changes: 2 additions & 0 deletions spec/src/main/asciidoc/microprofile-openapi-spec.adoc
Expand Up @@ -232,8 +232,10 @@ The following annotations are found in the https://github.com/eclipse/microprofi
| https://github.com/eclipse/microprofile-open-api/blob/master/api/src/main/java/org/eclipse/microprofile/openapi/annotations/parameters/Parameter.java[@Parameter] | Describes a single operation parameter.
| https://github.com/eclipse/microprofile-open-api/blob/master/api/src/main/java/org/eclipse/microprofile/openapi/annotations/parameters/Parameters.java[@Parameters] | Encapsulates input parameters.
| https://github.com/eclipse/microprofile-open-api/blob/master/api/src/main/java/org/eclipse/microprofile/openapi/annotations/parameters/RequestBody.java[@RequestBody] | Describes a single request body.
| https://github.com/eclipse/microprofile-open-api/blob/master/api/src/main/java/org/eclipse/microprofile/openapi/annotations/parameters/RequestBodySchema.java[@RequestBodySchema] | Describes a single request body with schema implementation class.
| https://github.com/eclipse/microprofile-open-api/blob/master/api/src/main/java/org/eclipse/microprofile/openapi/annotations/responses/APIResponse.java[@APIResponse] | Describes a single response from an API operation.
| https://github.com/eclipse/microprofile-open-api/blob/master/api/src/main/java/org/eclipse/microprofile/openapi/annotations/responses/APIResponses.java[@APIResponses] | A container for multiple responses from an API operation.
| https://github.com/eclipse/microprofile-open-api/blob/master/api/src/main/java/org/eclipse/microprofile/openapi/annotations/responses/APIResponseSchema.java[@APIResponseSchema] | Describes a single response with schema implementation class from an API operation.
| https://github.com/eclipse/microprofile-open-api/blob/master/api/src/main/java/org/eclipse/microprofile/openapi/annotations/security/OAuthFlow.java[@OAuthFlow] | Configuration details for a supported OAuth Flow.
| https://github.com/eclipse/microprofile-open-api/blob/master/api/src/main/java/org/eclipse/microprofile/openapi/annotations/security/OAuthFlows.java[@OAuthFlows] | Allows configuration of the supported OAuth Flows.
| https://github.com/eclipse/microprofile-open-api/blob/master/api/src/main/java/org/eclipse/microprofile/openapi/annotations/security/OAuthScope.java[@OAuthScope] | Represents an OAuth scope.
Expand Down
Expand Up @@ -30,8 +30,10 @@
import org.eclipse.microprofile.openapi.annotations.media.Content;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.annotations.parameters.RequestBody;
import org.eclipse.microprofile.openapi.annotations.parameters.RequestBodySchema;
import org.eclipse.microprofile.openapi.annotations.media.ExampleObject;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponseSchema;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.eclipse.microprofile.openapi.annotations.callbacks.CallbackOperation;

Expand Down Expand Up @@ -361,6 +363,7 @@ public Response findPetsByStatus(

@GET
@Path("/findByTags")
@Produces("application/json")
@Callback(
name = "tagsCallback",
callbackUrlExpression = "http://petstoreapp.com/pet",
Expand All @@ -383,6 +386,7 @@ public Response findPetsByStatus(
}
)
)
@APIResponseSchema(Pet[].class)
@Deprecated
public Response findPetsByTags(
@HeaderParam("apiKey") String apiKey,
Expand Down Expand Up @@ -438,4 +442,39 @@ public Response updatePetWithForm (
return Response.status(404).build();
}
}

@POST
@Path("/{petId}")
@Consumes({ "text/csv" })
@Produces({ "text/csv" })
@APIResponseSchema(value = Pet.class, responseCode = "204")
@Operation(summary = "Updates a pet in the store with CSV data")
public Response updatePetWithCsv (
@Parameter(
name = "petId",
description = "ID of pet that needs to be updated",
required = true)
@PathParam("petId") Long petId,
@RequestBodySchema(Pet.class)
String commaSeparatedValues
) {
Pet pet = petData.getPetById(petId);
if(pet != null) {
String[] values = commaSeparatedValues.split(",");
String name = values[2];
String status = values[5];

if(name != null && !"".equals(name)){
pet.setName(name);
}
if(status != null && !"".equals(status)){
pet.setStatus(status);
}
petData.addPet(pet);
return Response.ok().build();
}
else{
return Response.status(404).build();
}
}
}
Expand Up @@ -31,6 +31,8 @@
import io.restassured.filter.Filter;
import io.restassured.http.ContentType;
import io.restassured.parsing.Parser;
import io.restassured.response.ExtractableResponse;
import io.restassured.response.Response;
import io.restassured.response.ValidatableResponse;

public abstract class AppTestBase extends Arquillian {
Expand Down Expand Up @@ -89,6 +91,27 @@ public ValidatableResponse callEndpoint(String type) {
return vr;
}

/**
* Lookup the object at the provided path in the response and if the object
* is a reference (contains a $ref property), return the reference path. If the
* object is not a reference, return the input path.
*
* @param vr the response
* @param path a path which may be a reference object (containing a $ref)
* @return the path the object references if present, else the input path
*/
public static String dereference(ValidatableResponse vr, String path) {
ExtractableResponse<Response> response = vr.extract();
String ref = response.path(path + ".$ref");

if (ref != null) {
return ref.replaceFirst("^#/?", "").replace('/', '.');
}
else {
return path;
}
}

@DataProvider(name = "formatProvider")
public Object[][] provide() {
return new Object[][] { { "JSON" }, { "YAML" } };
Expand Down