2015-07-07 12 views
9

Ho un WidgetDto che ho annotato con annotazioni UI swagger. La risposta finale racchiude un elenco di WidgetDtos con uno strato di metadati (per pagina 21 del documento RESTful delle best practice di this). Per esempio:Documentare una risposta REST avvolta usando l'interfaccia utente swagger

{ 
    "data" : [ 
    { 
     "id" : 1234, 
     "prop1" : "val1" 
     ... 
    }, 
    { 
     "id" : 5678, 
     "prop1" : "val2" 
     ... 
    }, 
    ... 
    ] 
} 

Il mio codice Java assomiglia a questo:

@GET 
@Produces(MediaType.APPLICATION_JSON) 
@ApiOperation(
     value = "Get all widgets.", 
     response = WidgetDto.class 
) 
@ApiResponses(value = { 
     @ApiResponse(code = 200, message = "Returns the list of widgets.") 
}) 
public Response getWidgets() { 
    List<WidgetDto> widgets; 
    ... 
    Map<String, Object> responseBody = new HashMap<>(); 
    responseBody.put("data", widgets); 
    return Response.ok(responseBody).build(); 
} 

vorrei riutilizzare questo modello su più risorse, e io non voglio creare lista DTOS per ogni tipo di risposta . C'è un modo elegante per usare lo swagger per documentare questi tipi di corpi di risposta?

+0

Si può creare una classe wrapper che rappresenta un elenco di 'WidgetDto' all'interno di un campo' dati'? –

+0

@tim_yates sì, ma spero che ci sia una soluzione riutilizzabile che non richiede la creazione di classi wrapper specifiche per ogni tipo di DTO. – Bill

risposta

0

I tuoi metadati non fanno parte della tua risorsa ma fanno parte della rappresentazione della tua risorsa.

Nel mio caso, i tipi di risposte sono 'application/hal+json' e 'application/json', ognuno di loro usano un involucro diverso con differenti metadatas. Per risolvere questo problema, ho creato un documento extern per spiegare questi due wrapper e per ognuno di essi, come una singola risorsa e un elenco di risorse sono rappresentati con i metadati.

penso che la mia scelta è corretta perché io separo la risorsa delle sue rappresentazioni (per pagina 7 'La manipolazione delle risorse attraverso le rappresentazioni' di this migliori pratiche RESTful documento)

Nel tuo caso, si restituisce un elenco di WidgetDtos , il livello dei metadati è una parte della rappresentazione della tua risorsa.

Tuttavia, è possibile utilizzare una classe generica come risorse e risorse utilizzate da spring-hateoas:

public class Resources<T> implements Iterable<T> { 
    private final Collection<T> content; 
    Resources(Iterable<T> content) { 
     this.content = new ArrayList<T>(); 
     for (T element : content) { 
      this.content.add(element); 
     } 
    } 
} 

e usarlo in questo modo:

@GET 
@Produces(MediaType.APPLICATION_JSON) 
@ApiOperation(
     value = "Get all widgets.", 
     response = WidgetDto.class 
) 
@ApiResponses(value = { 
     @ApiResponse(code = 200, message = "Returns the list of widgets.") 
}) 
public Response getWidgets() { 
    List<WidgetDto> widgets; 
    ... 
    return Response.ok(new Resources<WidgetDto>(widgets)).build(); 
} 
0

ho affrontato un problema simile a pochi mesi fa, quando Stavo sviluppando un progetto per la scuola. La soluzione è creare una busta e restituirla sempre. La busta conterrà un "dati" di tipo "generico"; quindi sarai in grado di associarlo a qualsiasi tipo di dati. Nota che, anche se l'ho usato, in seguito ho letto che dovrebbe essere usato in modo scarso (penso che il tuo caso sia un buon esempio di utilizzo) ma tecnicamente un oggetto Eccezione dovrebbe essere lanciato se la richiesta non è riuscita.

Comunque questa è la mia classe di risposta che ho usato per riportare tutte le mie risposte:

public class Response <AnyData> { 

    private static final String SUCCESS = "success"; 
    private static final String FAILURE = "failure"; 

    private String status; 
    private AnyData data; 
    private String error; 

    private Response(String status, AnyData data, String error) { 
     this.status = status; 
     this.data = data; 
     this.error = error;; 
    } 

    private Response(String status, AnyData data) { 
     this(status, data,""); 
    } 

    private Response(String status, String error) { 
     this(status, null, error); 
    } 

    public static <AnyData> Response<AnyData> success(AnyData data) { 
     return new Response<AnyData>(SUCCESS, data); 
    } 

    public static <AnyData> Response<AnyData> failure(String error) { 
     return new Response<AnyData>(FAILURE, error); 
    } 

    public static <AnyData> Response<AnyData> unimplemented() { 
     return new Response<AnyData>(FAILURE, "Missing implementation in the backend."); 
    } 

    public static <AnyData> Response<AnyData> failureUserNotFound() { 
     return Response.failure("User not found!"); 
    } 

    public static <AnyData> Response<AnyData> failureBusinessNotFound() { 
     return Response.failure("Business not found!"); 
    } 

    // Removed getters and setters for simplicity. 
} 

Dopo questo è impostato ci limiteremo a creare le giuste risposte dalla comtroller. L'ho modificato un po 'per farlo funzionare con il campione dovrebbe essere abbastanza leggibile. Si noti che ho metodi statici per le mie risposte: 'il successo()', 'errore()' ...

@RestController 
@Api(tags={"Widgets"}) 
public class WidgetController { 

    @RequestMapping(value="/api/widgets", method=RequestMethod.GET, produces=MediaType.APPLICATION_JSON) 
    @ApiOperation(value = "Get all widgets.") 
    @ApiResponses(value = { 
      @ApiResponse(code = 200, message = "Returns the list of widgets.") 
    }) 
    public Response<List<WidgetDto>> getWidgets() { 
     List<WidgetDto> widgets = new LinkedList<>(); 
     widgets.add(new WidgetDto(1234, "val1")); 
     widgets.add(new WidgetDto(5678, "val2")); 

     return Response.success(widgets); 
    } 
} 

E qui è un campione del corpo della risposta: enter image description here

Spero che questo aiuti .