Provide API guide examples alongside existing getting started guides
This commit is contained in:
@@ -27,10 +27,11 @@ $ cd rest-notes-spring-data-rest
|
||||
$ ./gradlew restDocumentation
|
||||
```
|
||||
|
||||
Once the build is complete, open the generated documentation:
|
||||
Once the build is complete, open on of the generated pieces HTML documentation:
|
||||
|
||||
```
|
||||
open build/asciidoc/main.html
|
||||
open build/asciidoc/getting-started-guide.html
|
||||
open build/asciidoc/api-guide.html
|
||||
```
|
||||
|
||||
Every example request and response in the documentation is auto-generated using custom
|
||||
|
||||
@@ -0,0 +1,404 @@
|
||||
= RESTful Notes API Guide
|
||||
Andy Wilkinson;
|
||||
:doctype: book
|
||||
:toc:
|
||||
:sectanchors:
|
||||
:sectlinks:
|
||||
:toclevels: 4
|
||||
:source-highlighter: highlightjs
|
||||
|
||||
[[overview]]
|
||||
= Overview
|
||||
|
||||
[[overview-http-verbs]]
|
||||
== HTTP verbs
|
||||
|
||||
RESTful notes tries to adhere as closely as possible to standard HTTP and REST conventions in its
|
||||
use of HTTP verbs.
|
||||
|
||||
|===
|
||||
| Verb | Usage
|
||||
|
||||
| `GET`
|
||||
| Used to retrieve a resource
|
||||
|
||||
| `POST`
|
||||
| Used to create a new resource
|
||||
|
||||
| `PATCH`
|
||||
| Used to update an existing resource, including partial updates
|
||||
|
||||
| `DELETE`
|
||||
| Used to delete an existing resource
|
||||
|===
|
||||
|
||||
[[overview-http-status-codes]]
|
||||
== HTTP status codes
|
||||
|
||||
RESTful notes tries to adhere as closely as possible to standard HTTP and REST conventions in its
|
||||
use of HTTP status codes.
|
||||
|
||||
|===
|
||||
| Status code | Usage
|
||||
|
||||
| `200 OK`
|
||||
| The request completed successfully
|
||||
|
||||
| `201 Created`
|
||||
| A new resource has been created successfully. The resource's URI is available from the response's
|
||||
`Location` header
|
||||
|
||||
| `204 No Content`
|
||||
| An update to an existing resource has been applied successfully
|
||||
|
||||
| `400 Bad Request`
|
||||
| The request was malformed. The response body will include an error providing further information
|
||||
|
||||
| `404 Not Found`
|
||||
| The requested resource did not exist
|
||||
|===
|
||||
|
||||
[[overview-errors]]
|
||||
== Errors
|
||||
|
||||
Whenever an error response (status code >= 400) is returned, the body will contain a JSON object
|
||||
that describes the problem. The error object has the following fields:
|
||||
|
||||
|===
|
||||
| Field | Description
|
||||
|
||||
| error
|
||||
| The HTTP error that occurred, e.g. `Bad Request`
|
||||
|
||||
| message
|
||||
| A description of the cause of the error
|
||||
|
||||
| path
|
||||
| The path to which the request was made
|
||||
|
||||
| status
|
||||
| The HTTP status code, e.g. `400`
|
||||
|
||||
| timestamp
|
||||
| The time, in milliseconds, at which the error occurred
|
||||
|===
|
||||
|
||||
For example, a request that attempts to apply a non-existent tag to a note will produce a
|
||||
`400 Bad Request` response:
|
||||
|
||||
include::{generated}/error-example/response.asciidoc[]
|
||||
|
||||
[[overview-hypermedia]]
|
||||
== Hypermedia
|
||||
|
||||
RESTful Notes uses hypermedia and resources include links to other resources in their
|
||||
responses. Responses are in http://stateless.co/hal_specification.html[Hypertext Application
|
||||
Language (HAL)] format. Links can be found benath the `_links` key. Users of the API should
|
||||
not created URIs themselves, instead they should use the above-described links to navigate
|
||||
from resource to resource.
|
||||
|
||||
[[resources]]
|
||||
= Resources
|
||||
|
||||
|
||||
|
||||
[[resources-index]]
|
||||
== Index
|
||||
|
||||
The index provides the entry point into the service.
|
||||
|
||||
|
||||
|
||||
[[resources-index-access]]
|
||||
=== Accessing the index
|
||||
|
||||
A `GET` request is used to access the index
|
||||
|
||||
==== Response structure
|
||||
|
||||
|===
|
||||
| JSON path | Description
|
||||
|
||||
| `_links`
|
||||
| <<resources-index-links,Links>> to other resources
|
||||
|===
|
||||
|
||||
==== Example response
|
||||
|
||||
include::{generated}/index-example/response.asciidoc[]
|
||||
|
||||
|
||||
|
||||
[[resources-index-links]]
|
||||
==== Links
|
||||
|
||||
|===
|
||||
| Relation | Description
|
||||
|
||||
| notes
|
||||
| The <<resources-notes,Notes resource>>
|
||||
|
||||
| tags
|
||||
| The <<resources-tags,Tags resource>>
|
||||
|===
|
||||
|
||||
|
||||
|
||||
[[resources-notes]]
|
||||
== Notes
|
||||
|
||||
The Notes resources is used to create and list notes
|
||||
|
||||
|
||||
|
||||
[[resources-notes-list]]
|
||||
=== Listing notes
|
||||
|
||||
A `GET` request will list all of the service's notes.
|
||||
|
||||
==== Response structure
|
||||
|
||||
|===
|
||||
| JSON path | Description
|
||||
|
||||
| `_embedded.notes`
|
||||
| An array of <<resources-note,Note resources>>
|
||||
|===
|
||||
|
||||
==== Example request
|
||||
|
||||
include::{generated}/notes-list-example/request.asciidoc[]
|
||||
|
||||
==== Example response
|
||||
|
||||
include::{generated}/notes-list-example/response.asciidoc[]
|
||||
|
||||
|
||||
|
||||
[[resources-notes-create]]
|
||||
=== Creating a note
|
||||
|
||||
A `POST` request is used to create a note
|
||||
|
||||
==== Request structure
|
||||
|
||||
|===
|
||||
| JSON path | Description
|
||||
|
||||
| `title`
|
||||
| The title of the note
|
||||
|
||||
| `body`
|
||||
| The body of the note
|
||||
|
||||
| `tags`
|
||||
| | The tags of the note as an array of URIs
|
||||
|===
|
||||
|
||||
==== Example request
|
||||
|
||||
include::{generated}/notes-create-example/request.asciidoc[]
|
||||
|
||||
==== Example response
|
||||
|
||||
include::{generated}/notes-create-example/response.asciidoc[]
|
||||
|
||||
|
||||
|
||||
[[resources-tags]]
|
||||
== Tags
|
||||
|
||||
The Tags resource is used to create and list tags.
|
||||
|
||||
|
||||
|
||||
[[resources-tags-list]]
|
||||
=== Listing tags
|
||||
|
||||
A `GET` request will list all of the service's tags.
|
||||
|
||||
==== Response structure
|
||||
|
||||
|===
|
||||
| JSON path | Description
|
||||
|
||||
| `_embedded.tags`
|
||||
| An array of <<resources-tag,Tag resources>>
|
||||
|===
|
||||
|
||||
==== Example request
|
||||
|
||||
include::{generated}/tags-list-example/request.asciidoc[]
|
||||
|
||||
==== Example response
|
||||
|
||||
include::{generated}/tags-list-example/response.asciidoc[]
|
||||
|
||||
|
||||
|
||||
[[resources-tags-create]]
|
||||
=== Creating a tag
|
||||
|
||||
A `POST` request is used to create a note
|
||||
|
||||
==== Request structure
|
||||
|
||||
|===
|
||||
| JSON path | Description
|
||||
|
||||
| `name`
|
||||
| The name of the tag
|
||||
|===
|
||||
|
||||
==== Example request
|
||||
|
||||
include::{generated}/tags-create-example/request.asciidoc[]
|
||||
|
||||
==== Example response
|
||||
|
||||
include::{generated}/tags-create-example/response.asciidoc[]
|
||||
|
||||
|
||||
|
||||
[[resources-note]]
|
||||
== Note
|
||||
|
||||
The Note resource is used to retrieve, update, and delete individual notes
|
||||
|
||||
|
||||
|
||||
[[resources-note-links]]
|
||||
=== Links
|
||||
|
||||
|===
|
||||
| Relation | Description
|
||||
|
||||
| self
|
||||
| This <<resources-note,note>>
|
||||
|
||||
| note-tags
|
||||
| This note's <<resources-tags,tags>>
|
||||
|===
|
||||
|
||||
|
||||
|
||||
[[resources-note-retrieve]]
|
||||
=== Retrieve a note
|
||||
|
||||
A `GET` request will retrieve the details of a note
|
||||
|
||||
Example response:
|
||||
|
||||
include::{generated}/note-get-example/response.asciidoc[]
|
||||
|
||||
|===
|
||||
| JSON path | Description
|
||||
|
||||
| `title`
|
||||
| The title of the note
|
||||
|
||||
| `body`
|
||||
| The body of the note
|
||||
|
||||
| `_links`
|
||||
| <<resources-note-links,Links>> to other resources
|
||||
|===
|
||||
|
||||
|
||||
|
||||
[[resources-note-update]]
|
||||
=== Update a note
|
||||
|
||||
A `PATCH` request is used to update a note
|
||||
|
||||
==== Request structure
|
||||
|
||||
|===
|
||||
| JSON path | Description
|
||||
|
||||
| `title`
|
||||
| The title of the note
|
||||
|
||||
| `body`
|
||||
| The body of the note
|
||||
|
||||
| `tags`
|
||||
| The tags of the note as an array of URIs
|
||||
|===
|
||||
|
||||
To leave an attribute of a note unchanged, any of the above may be omitted from the request.
|
||||
|
||||
==== Example request
|
||||
|
||||
include::{generated}/note-update-example/request.asciidoc[]
|
||||
|
||||
==== Example response
|
||||
|
||||
include::{generated}/note-update-example/response.asciidoc[]
|
||||
|
||||
|
||||
[[resources-note]]
|
||||
== Tag
|
||||
|
||||
The Tag resource is used to retrieve, update, and delete individual tags
|
||||
|
||||
|
||||
|
||||
[[resources-tag-links]]
|
||||
=== Links
|
||||
|
||||
|===
|
||||
| Relation | Description
|
||||
|
||||
| self
|
||||
| This <<resources-tag,tag>>
|
||||
|
||||
| notes
|
||||
| The <<resources-notes,notes>> that have this tag
|
||||
|===
|
||||
|
||||
|
||||
|
||||
[[resources-tag-retrieve]]
|
||||
=== Retrieve a tag
|
||||
|
||||
A `GET` request will retrieve the details of a tag
|
||||
|
||||
Example response:
|
||||
|
||||
include::{generated}/tag-get-example/response.asciidoc[]
|
||||
|
||||
|===
|
||||
| JSON path | Description
|
||||
|
||||
| `name`
|
||||
| The name of the tag
|
||||
|
||||
| `_links`
|
||||
| <<resources-tag-links,Links>> to other resources
|
||||
|===
|
||||
|
||||
|
||||
|
||||
[[resources-tag-update]]
|
||||
=== Update a tag
|
||||
|
||||
A `PATCH` request is used to update a tag
|
||||
|
||||
==== Request structure
|
||||
|
||||
|===
|
||||
| JSON path | Description
|
||||
|
||||
| `name`
|
||||
| The name of the tag
|
||||
|
||||
|===
|
||||
|
||||
==== Example request
|
||||
|
||||
include::{generated}/tag-update-example/request.asciidoc[]
|
||||
|
||||
==== Example response
|
||||
|
||||
include::{generated}/tag-update-example/response.asciidoc[]
|
||||
@@ -0,0 +1,297 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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 com.example.notes;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.springframework.restdocs.core.RestDocumentation.document;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.RequestDispatcher;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
import org.springframework.hateoas.MediaTypes;
|
||||
import org.springframework.restdocs.core.RestDocumentationConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(classes = RestNotesSpringDataRest.class)
|
||||
@WebAppConfiguration
|
||||
public class ApiDocumentation {
|
||||
|
||||
@Autowired
|
||||
private NoteRepository noteRepository;
|
||||
|
||||
@Autowired
|
||||
private TagRepository tagRepository;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Autowired
|
||||
private WebApplicationContext context;
|
||||
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
|
||||
.apply(new RestDocumentationConfiguration()).build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void errorExample() throws Exception {
|
||||
document(
|
||||
"error-example",
|
||||
this.mockMvc
|
||||
.perform(get("/error")
|
||||
.requestAttr(RequestDispatcher.ERROR_STATUS_CODE, 400)
|
||||
.requestAttr(RequestDispatcher.ERROR_REQUEST_URI,
|
||||
"/notes")
|
||||
.requestAttr(RequestDispatcher.ERROR_MESSAGE,
|
||||
"The tag 'http://localhost:8080/tags/123' does not exist")))
|
||||
.andDo(print()).andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath("error", is("Bad Request")))
|
||||
.andExpect(jsonPath("timestamp", is(notNullValue())))
|
||||
.andExpect(jsonPath("status", is(400)))
|
||||
.andExpect(jsonPath("path", is(notNullValue())));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexExample() throws Exception {
|
||||
document("index-example",
|
||||
this.mockMvc.perform(get("/"))
|
||||
.andExpect(status().isOk()))
|
||||
.andExpect(jsonPath("_links.notes", is(notNullValue())))
|
||||
.andExpect(jsonPath("_links.tags", is(notNullValue())));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notesListExample() throws Exception {
|
||||
this.noteRepository.deleteAll();
|
||||
|
||||
createNote("REST maturity model",
|
||||
"http://martinfowler.com/articles/richardsonMaturityModel.html");
|
||||
createNote("Hypertext Application Language (HAL)",
|
||||
"http://stateless.co/hal_specification.html");
|
||||
createNote("Application-Level Profile Semantics (ALPS)", "http://alps.io/spec/");
|
||||
|
||||
document("notes-list-example", this.mockMvc.perform(get("/notes"))).andExpect(
|
||||
status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notesCreateExample() throws Exception {
|
||||
Map<String, String> tag = new HashMap<String, String>();
|
||||
tag.put("name", "REST");
|
||||
|
||||
String tagLocation = this.mockMvc
|
||||
.perform(
|
||||
post("/tags").contentType(MediaTypes.HAL_JSON).content(
|
||||
this.objectMapper.writeValueAsString(tag)))
|
||||
.andExpect(status().isCreated()).andReturn().getResponse()
|
||||
.getHeader("Location");
|
||||
|
||||
Map<String, Object> note = new HashMap<String, Object>();
|
||||
note.put("title", "REST maturity model");
|
||||
note.put("body", "http://martinfowler.com/articles/richardsonMaturityModel.html");
|
||||
note.put("tags", Arrays.asList(tagLocation));
|
||||
|
||||
document(
|
||||
"notes-create-example",
|
||||
this.mockMvc.perform(
|
||||
post("/notes").contentType(MediaTypes.HAL_JSON).content(
|
||||
this.objectMapper.writeValueAsString(note))).andExpect(
|
||||
status().isCreated()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noteGetExample() throws Exception {
|
||||
Map<String, String> tag = new HashMap<String, String>();
|
||||
tag.put("name", "REST");
|
||||
|
||||
String tagLocation = this.mockMvc
|
||||
.perform(
|
||||
post("/tags").contentType(MediaTypes.HAL_JSON).content(
|
||||
this.objectMapper.writeValueAsString(tag)))
|
||||
.andExpect(status().isCreated()).andReturn().getResponse()
|
||||
.getHeader("Location");
|
||||
|
||||
Map<String, Object> note = new HashMap<String, Object>();
|
||||
note.put("title", "REST maturity model");
|
||||
note.put("body", "http://martinfowler.com/articles/richardsonMaturityModel.html");
|
||||
note.put("tags", Arrays.asList(tagLocation));
|
||||
|
||||
String noteLocation = this.mockMvc
|
||||
.perform(
|
||||
post("/notes").contentType(MediaTypes.HAL_JSON).content(
|
||||
this.objectMapper.writeValueAsString(note)))
|
||||
.andExpect(status().isCreated()).andReturn().getResponse()
|
||||
.getHeader("Location");
|
||||
|
||||
document("note-get-example", this.mockMvc.perform(get(noteLocation)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("title", is(note.get("title"))))
|
||||
.andExpect(jsonPath("body", is(note.get("body"))))
|
||||
.andExpect(jsonPath("_links.self.href", is(noteLocation)))
|
||||
.andExpect(jsonPath("_links.tags", is(notNullValue())));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tagsListExample() throws Exception {
|
||||
this.noteRepository.deleteAll();
|
||||
this.tagRepository.deleteAll();
|
||||
|
||||
createTag("REST");
|
||||
createTag("Hypermedia");
|
||||
createTag("HTTP");
|
||||
|
||||
document("tags-list-example", this.mockMvc.perform(get("/tags"))).andExpect(
|
||||
status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tagsCreateExample() throws Exception {
|
||||
Map<String, String> tag = new HashMap<String, String>();
|
||||
tag.put("name", "REST");
|
||||
|
||||
document(
|
||||
"tags-create-example",
|
||||
this.mockMvc.perform(
|
||||
post("/tags").contentType(MediaTypes.HAL_JSON).content(
|
||||
this.objectMapper.writeValueAsString(tag))).andExpect(
|
||||
status().isCreated()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noteUpdateExample() throws Exception {
|
||||
Map<String, Object> note = new HashMap<String, Object>();
|
||||
note.put("title", "REST maturity model");
|
||||
note.put("body", "http://martinfowler.com/articles/richardsonMaturityModel.html");
|
||||
|
||||
String noteLocation = this.mockMvc
|
||||
.perform(
|
||||
post("/notes").contentType(MediaTypes.HAL_JSON).content(
|
||||
this.objectMapper.writeValueAsString(note)))
|
||||
.andExpect(status().isCreated()).andReturn().getResponse()
|
||||
.getHeader("Location");
|
||||
|
||||
this.mockMvc.perform(get(noteLocation)).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("title", is(note.get("title"))))
|
||||
.andExpect(jsonPath("body", is(note.get("body"))))
|
||||
.andExpect(jsonPath("_links.self.href", is(noteLocation)))
|
||||
.andExpect(jsonPath("_links.tags", is(notNullValue())));
|
||||
|
||||
Map<String, String> tag = new HashMap<String, String>();
|
||||
tag.put("name", "REST");
|
||||
|
||||
String tagLocation = this.mockMvc
|
||||
.perform(
|
||||
post("/tags").contentType(MediaTypes.HAL_JSON).content(
|
||||
this.objectMapper.writeValueAsString(tag)))
|
||||
.andExpect(status().isCreated()).andReturn().getResponse()
|
||||
.getHeader("Location");
|
||||
|
||||
Map<String, Object> noteUpdate = new HashMap<String, Object>();
|
||||
noteUpdate.put("tags", Arrays.asList(tagLocation));
|
||||
|
||||
document(
|
||||
"note-update-example",
|
||||
this.mockMvc.perform(
|
||||
patch(noteLocation).contentType(MediaTypes.HAL_JSON).content(
|
||||
this.objectMapper.writeValueAsString(noteUpdate)))
|
||||
.andExpect(status().isNoContent()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tagGetExample() throws Exception {
|
||||
Map<String, String> tag = new HashMap<String, String>();
|
||||
tag.put("name", "REST");
|
||||
|
||||
String tagLocation = this.mockMvc
|
||||
.perform(
|
||||
post("/tags").contentType(MediaTypes.HAL_JSON).content(
|
||||
this.objectMapper.writeValueAsString(tag)))
|
||||
.andExpect(status().isCreated()).andReturn().getResponse()
|
||||
.getHeader("Location");
|
||||
|
||||
document("tag-get-example", this.mockMvc.perform(get(tagLocation)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("name", is(tag.get("name"))))
|
||||
.andExpect(jsonPath("_links.self.href", is(tagLocation)))
|
||||
.andExpect(jsonPath("_links.notes", is(notNullValue())));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tagUpdateExample() throws Exception {
|
||||
Map<String, String> tag = new HashMap<String, String>();
|
||||
tag.put("name", "REST");
|
||||
|
||||
String tagLocation = this.mockMvc
|
||||
.perform(
|
||||
post("/tags").contentType(MediaTypes.HAL_JSON).content(
|
||||
this.objectMapper.writeValueAsString(tag)))
|
||||
.andExpect(status().isCreated()).andReturn().getResponse()
|
||||
.getHeader("Location");
|
||||
|
||||
Map<String, Object> tagUpdate = new HashMap<String, Object>();
|
||||
tagUpdate.put("name", "RESTful");
|
||||
|
||||
document(
|
||||
"tag-update-example",
|
||||
this.mockMvc.perform(
|
||||
patch(tagLocation).contentType(MediaTypes.HAL_JSON).content(
|
||||
this.objectMapper.writeValueAsString(tagUpdate)))
|
||||
.andExpect(status().isNoContent()));
|
||||
}
|
||||
|
||||
private void createNote(String title, String body) {
|
||||
Note note = new Note();
|
||||
note.setTitle(title);
|
||||
note.setBody(body);
|
||||
|
||||
this.noteRepository.save(note);
|
||||
}
|
||||
|
||||
private void createTag(String name) {
|
||||
Tag tag = new Tag();
|
||||
tag.setName(name);
|
||||
this.tagRepository.save(tag);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,404 @@
|
||||
= RESTful Notes API Guide
|
||||
Andy Wilkinson;
|
||||
:doctype: book
|
||||
:toc:
|
||||
:sectanchors:
|
||||
:sectlinks:
|
||||
:toclevels: 4
|
||||
:source-highlighter: highlightjs
|
||||
|
||||
[[overview]]
|
||||
= Overview
|
||||
|
||||
[[overview-http-verbs]]
|
||||
== HTTP verbs
|
||||
|
||||
RESTful notes tries to adhere as closely as possible to standard HTTP and REST conventions in its
|
||||
use of HTTP verbs.
|
||||
|
||||
|===
|
||||
| Verb | Usage
|
||||
|
||||
| `GET`
|
||||
| Used to retrieve a resource
|
||||
|
||||
| `POST`
|
||||
| Used to create a new resource
|
||||
|
||||
| `PATCH`
|
||||
| Used to update an existing resource, including partial updates
|
||||
|
||||
| `DELETE`
|
||||
| Used to delete an existing resource
|
||||
|===
|
||||
|
||||
[[overview-http-status-codes]]
|
||||
== HTTP status codes
|
||||
|
||||
RESTful notes tries to adhere as closely as possible to standard HTTP and REST conventions in its
|
||||
use of HTTP status codes.
|
||||
|
||||
|===
|
||||
| Status code | Usage
|
||||
|
||||
| `200 OK`
|
||||
| The request completed successfully
|
||||
|
||||
| `201 Created`
|
||||
| A new resource has been created successfully. The resource's URI is available from the response's
|
||||
`Location` header
|
||||
|
||||
| `204 No Content`
|
||||
| An update to an existing resource has been applied successfully
|
||||
|
||||
| `400 Bad Request`
|
||||
| The request was malformed. The response body will include an error providing further information
|
||||
|
||||
| `404 Not Found`
|
||||
| The requested resource did not exist
|
||||
|===
|
||||
|
||||
[[overview-errors]]
|
||||
== Errors
|
||||
|
||||
Whenever an error response (status code >= 400) is returned, the body will contain a JSON object
|
||||
that describes the problem. The error object has the following fields:
|
||||
|
||||
|===
|
||||
| Field | Description
|
||||
|
||||
| error
|
||||
| The HTTP error that occurred, e.g. `Bad Request`
|
||||
|
||||
| message
|
||||
| A description of the cause of the error
|
||||
|
||||
| path
|
||||
| The path to which the request was made
|
||||
|
||||
| status
|
||||
| The HTTP status code, e.g. `400`
|
||||
|
||||
| timestamp
|
||||
| The time, in milliseconds, at which the error occurred
|
||||
|===
|
||||
|
||||
For example, a request that attempts to apply a non-existent tag to a note will produce a
|
||||
`400 Bad Request` response:
|
||||
|
||||
include::{generated}/error-example/response.asciidoc[]
|
||||
|
||||
[[overview-hypermedia]]
|
||||
== Hypermedia
|
||||
|
||||
RESTful Notes uses hypermedia and resources include links to other resources in their
|
||||
responses. Responses are in http://stateless.co/hal_specification.html[Hypertext Application
|
||||
Language (HAL)] format. Links can be found benath the `_links` key. Users of the API should
|
||||
not created URIs themselves, instead they should use the above-described links to navigate
|
||||
from resource to resource.
|
||||
|
||||
[[resources]]
|
||||
= Resources
|
||||
|
||||
|
||||
|
||||
[[resources-index]]
|
||||
== Index
|
||||
|
||||
The index provides the entry point into the service.
|
||||
|
||||
|
||||
|
||||
[[resources-index-access]]
|
||||
=== Accessing the index
|
||||
|
||||
A `GET` request is used to access the index
|
||||
|
||||
==== Response structure
|
||||
|
||||
|===
|
||||
| JSON path | Description
|
||||
|
||||
| `_links`
|
||||
| <<resources-index-links,Links>> to other resources
|
||||
|===
|
||||
|
||||
==== Example response
|
||||
|
||||
include::{generated}/index-example/response.asciidoc[]
|
||||
|
||||
|
||||
|
||||
[[resources-index-links]]
|
||||
==== Links
|
||||
|
||||
|===
|
||||
| Relation | Description
|
||||
|
||||
| notes
|
||||
| The <<resources-notes,Notes resource>>
|
||||
|
||||
| tags
|
||||
| The <<resources-tags,Tags resource>>
|
||||
|===
|
||||
|
||||
|
||||
|
||||
[[resources-notes]]
|
||||
== Notes
|
||||
|
||||
The Notes resources is used to create and list notes
|
||||
|
||||
|
||||
|
||||
[[resources-notes-list]]
|
||||
=== Listing notes
|
||||
|
||||
A `GET` request will list all of the service's notes.
|
||||
|
||||
==== Response structure
|
||||
|
||||
|===
|
||||
| JSON path | Description
|
||||
|
||||
| `_embedded.notes`
|
||||
| An array of <<resources-note,Note resources>>
|
||||
|===
|
||||
|
||||
==== Example request
|
||||
|
||||
include::{generated}/notes-list-example/request.asciidoc[]
|
||||
|
||||
==== Example response
|
||||
|
||||
include::{generated}/notes-list-example/response.asciidoc[]
|
||||
|
||||
|
||||
|
||||
[[resources-notes-create]]
|
||||
=== Creating a note
|
||||
|
||||
A `POST` request is used to create a note
|
||||
|
||||
==== Request structure
|
||||
|
||||
|===
|
||||
| JSON path | Description
|
||||
|
||||
| `title`
|
||||
| The title of the note
|
||||
|
||||
| `body`
|
||||
| The body of the note
|
||||
|
||||
| `tags`
|
||||
| | The tags of the note as an array of URIs
|
||||
|===
|
||||
|
||||
==== Example request
|
||||
|
||||
include::{generated}/notes-create-example/request.asciidoc[]
|
||||
|
||||
==== Example response
|
||||
|
||||
include::{generated}/notes-create-example/response.asciidoc[]
|
||||
|
||||
|
||||
|
||||
[[resources-tags]]
|
||||
== Tags
|
||||
|
||||
The Tags resource is used to create and list tags.
|
||||
|
||||
|
||||
|
||||
[[resources-tags-list]]
|
||||
=== Listing tags
|
||||
|
||||
A `GET` request will list all of the service's tags.
|
||||
|
||||
==== Response structure
|
||||
|
||||
|===
|
||||
| JSON path | Description
|
||||
|
||||
| `_embedded.tags`
|
||||
| An array of <<resources-tag,Tag resources>>
|
||||
|===
|
||||
|
||||
==== Example request
|
||||
|
||||
include::{generated}/tags-list-example/request.asciidoc[]
|
||||
|
||||
==== Example response
|
||||
|
||||
include::{generated}/tags-list-example/response.asciidoc[]
|
||||
|
||||
|
||||
|
||||
[[resources-tags-create]]
|
||||
=== Creating a tag
|
||||
|
||||
A `POST` request is used to create a note
|
||||
|
||||
==== Request structure
|
||||
|
||||
|===
|
||||
| JSON path | Description
|
||||
|
||||
| `name`
|
||||
| The name of the tag
|
||||
|===
|
||||
|
||||
==== Example request
|
||||
|
||||
include::{generated}/tags-create-example/request.asciidoc[]
|
||||
|
||||
==== Example response
|
||||
|
||||
include::{generated}/tags-create-example/response.asciidoc[]
|
||||
|
||||
|
||||
|
||||
[[resources-note]]
|
||||
== Note
|
||||
|
||||
The Note resource is used to retrieve, update, and delete individual notes
|
||||
|
||||
|
||||
|
||||
[[resources-note-links]]
|
||||
=== Links
|
||||
|
||||
|===
|
||||
| Relation | Description
|
||||
|
||||
| self
|
||||
| This <<resources-note,note>>
|
||||
|
||||
| note-tags
|
||||
| This note's <<resources-note-tags,tags>>
|
||||
|===
|
||||
|
||||
|
||||
|
||||
[[resources-note-retrieve]]
|
||||
=== Retrieve a note
|
||||
|
||||
A `GET` request will retrieve the details of a note
|
||||
|
||||
Example response:
|
||||
|
||||
include::{generated}/note-get-example/response.asciidoc[]
|
||||
|
||||
|===
|
||||
| JSON path | Description
|
||||
|
||||
| `title`
|
||||
| The title of the note
|
||||
|
||||
| `body`
|
||||
| The body of the note
|
||||
|
||||
| `_links`
|
||||
| <<resources-note-links,Links>> to other resources
|
||||
|===
|
||||
|
||||
|
||||
|
||||
[[resources-note-update]]
|
||||
=== Update a note
|
||||
|
||||
A `PATCH` request is used to update a note
|
||||
|
||||
==== Request structure
|
||||
|
||||
|===
|
||||
| JSON path | Description
|
||||
|
||||
| `title`
|
||||
| The title of the note
|
||||
|
||||
| `body`
|
||||
| The body of the note
|
||||
|
||||
| `tags`
|
||||
| The tags of the note as an array of URIs
|
||||
|===
|
||||
|
||||
To leave an attribute of a note unchanged, any of the above may be omitted from the request.
|
||||
|
||||
==== Example request
|
||||
|
||||
include::{generated}/note-update-example/request.asciidoc[]
|
||||
|
||||
==== Example response
|
||||
|
||||
include::{generated}/note-update-example/response.asciidoc[]
|
||||
|
||||
|
||||
[[resources-note]]
|
||||
== Tag
|
||||
|
||||
The Tag resource is used to retrieve, update, and delete individual tags
|
||||
|
||||
|
||||
|
||||
[[resources-tag-links]]
|
||||
=== Links
|
||||
|
||||
|===
|
||||
| Relation | Description
|
||||
|
||||
| self
|
||||
| This <<resources-tag,tag>>
|
||||
|
||||
| tagged-notes
|
||||
| The <<resources-tagged-notes,notes>> that have this tag
|
||||
|===
|
||||
|
||||
|
||||
|
||||
[[resources-tag-retrieve]]
|
||||
=== Retrieve a tag
|
||||
|
||||
A `GET` request will retrieve the details of a tag
|
||||
|
||||
Example response:
|
||||
|
||||
include::{generated}/tag-get-example/response.asciidoc[]
|
||||
|
||||
|===
|
||||
| JSON path | Description
|
||||
|
||||
| `name`
|
||||
| The name of the tag
|
||||
|
||||
| `_links`
|
||||
| <<resources-tag-links,Links>> to other resources
|
||||
|===
|
||||
|
||||
|
||||
|
||||
[[resources-tag-update]]
|
||||
=== Update a tag
|
||||
|
||||
A `PATCH` request is used to update a tag
|
||||
|
||||
==== Request structure
|
||||
|
||||
|===
|
||||
| JSON path | Description
|
||||
|
||||
| `name`
|
||||
| The name of the tag
|
||||
|
||||
|===
|
||||
|
||||
==== Example request
|
||||
|
||||
include::{generated}/tag-update-example/request.asciidoc[]
|
||||
|
||||
==== Example response
|
||||
|
||||
include::{generated}/tag-update-example/response.asciidoc[]
|
||||
@@ -1,4 +1,4 @@
|
||||
= RESTful Notes User Guide
|
||||
= RESTful Notes Getting Started Guide
|
||||
Andy Wilkinson;
|
||||
:doctype: book
|
||||
:toc:
|
||||
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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 com.example.notes;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.springframework.restdocs.core.RestDocumentation.document;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.RequestDispatcher;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
import org.springframework.hateoas.MediaTypes;
|
||||
import org.springframework.restdocs.core.RestDocumentationConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(classes = RestNotesSpringHateoas.class)
|
||||
@WebAppConfiguration
|
||||
public class ApiDocumentation {
|
||||
|
||||
@Autowired
|
||||
private NoteRepository noteRepository;
|
||||
|
||||
@Autowired
|
||||
private TagRepository tagRepository;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Autowired
|
||||
private WebApplicationContext context;
|
||||
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
|
||||
.apply(new RestDocumentationConfiguration()).build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void errorExample() throws Exception {
|
||||
document(
|
||||
"error-example",
|
||||
this.mockMvc
|
||||
.perform(get("/error")
|
||||
.requestAttr(RequestDispatcher.ERROR_STATUS_CODE, 400)
|
||||
.requestAttr(RequestDispatcher.ERROR_REQUEST_URI,
|
||||
"/notes")
|
||||
.requestAttr(RequestDispatcher.ERROR_MESSAGE,
|
||||
"The tag 'http://localhost:8080/tags/123' does not exist")))
|
||||
.andDo(print()).andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath("error", is("Bad Request")))
|
||||
.andExpect(jsonPath("timestamp", is(notNullValue())))
|
||||
.andExpect(jsonPath("status", is(400)))
|
||||
.andExpect(jsonPath("path", is(notNullValue())));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexExample() throws Exception {
|
||||
document("index-example",
|
||||
this.mockMvc.perform(get("/")).andExpect(status().isOk()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notesListExample() throws Exception {
|
||||
this.noteRepository.deleteAll();
|
||||
|
||||
createNote("REST maturity model",
|
||||
"http://martinfowler.com/articles/richardsonMaturityModel.html");
|
||||
createNote("Hypertext Application Language (HAL)",
|
||||
"http://stateless.co/hal_specification.html");
|
||||
createNote("Application-Level Profile Semantics (ALPS)", "http://alps.io/spec/");
|
||||
|
||||
document("notes-list-example", this.mockMvc.perform(get("/notes"))).andExpect(
|
||||
status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notesCreateExample() throws Exception {
|
||||
Map<String, String> tag = new HashMap<String, String>();
|
||||
tag.put("name", "REST");
|
||||
|
||||
String tagLocation = this.mockMvc
|
||||
.perform(
|
||||
post("/tags").contentType(MediaTypes.HAL_JSON).content(
|
||||
this.objectMapper.writeValueAsString(tag)))
|
||||
.andExpect(status().isCreated()).andReturn().getResponse()
|
||||
.getHeader("Location");
|
||||
|
||||
Map<String, Object> note = new HashMap<String, Object>();
|
||||
note.put("title", "REST maturity model");
|
||||
note.put("body", "http://martinfowler.com/articles/richardsonMaturityModel.html");
|
||||
note.put("tags", Arrays.asList(tagLocation));
|
||||
|
||||
document(
|
||||
"notes-create-example",
|
||||
this.mockMvc.perform(
|
||||
post("/notes").contentType(MediaTypes.HAL_JSON).content(
|
||||
this.objectMapper.writeValueAsString(note))).andExpect(
|
||||
status().isCreated()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noteGetExample() throws Exception {
|
||||
Map<String, String> tag = new HashMap<String, String>();
|
||||
tag.put("name", "REST");
|
||||
|
||||
String tagLocation = this.mockMvc
|
||||
.perform(
|
||||
post("/tags").contentType(MediaTypes.HAL_JSON).content(
|
||||
this.objectMapper.writeValueAsString(tag)))
|
||||
.andExpect(status().isCreated()).andReturn().getResponse()
|
||||
.getHeader("Location");
|
||||
|
||||
Map<String, Object> note = new HashMap<String, Object>();
|
||||
note.put("title", "REST maturity model");
|
||||
note.put("body", "http://martinfowler.com/articles/richardsonMaturityModel.html");
|
||||
note.put("tags", Arrays.asList(tagLocation));
|
||||
|
||||
String noteLocation = this.mockMvc
|
||||
.perform(
|
||||
post("/notes").contentType(MediaTypes.HAL_JSON).content(
|
||||
this.objectMapper.writeValueAsString(note)))
|
||||
.andExpect(status().isCreated()).andReturn().getResponse()
|
||||
.getHeader("Location");
|
||||
|
||||
document("note-get-example", this.mockMvc.perform(get(noteLocation)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("title", is(note.get("title"))))
|
||||
.andExpect(jsonPath("body", is(note.get("body"))))
|
||||
.andExpect(jsonPath("_links.self.href", is(noteLocation)))
|
||||
.andExpect(jsonPath("_links.note-tags", is(notNullValue())));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tagsListExample() throws Exception {
|
||||
this.noteRepository.deleteAll();
|
||||
this.tagRepository.deleteAll();
|
||||
|
||||
createTag("REST");
|
||||
createTag("Hypermedia");
|
||||
createTag("HTTP");
|
||||
|
||||
document("tags-list-example", this.mockMvc.perform(get("/tags"))).andExpect(
|
||||
status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tagsCreateExample() throws Exception {
|
||||
Map<String, String> tag = new HashMap<String, String>();
|
||||
tag.put("name", "REST");
|
||||
|
||||
document(
|
||||
"tags-create-example",
|
||||
this.mockMvc.perform(
|
||||
post("/tags").contentType(MediaTypes.HAL_JSON).content(
|
||||
this.objectMapper.writeValueAsString(tag))).andExpect(
|
||||
status().isCreated()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noteUpdateExample() throws Exception {
|
||||
Map<String, Object> note = new HashMap<String, Object>();
|
||||
note.put("title", "REST maturity model");
|
||||
note.put("body", "http://martinfowler.com/articles/richardsonMaturityModel.html");
|
||||
|
||||
String noteLocation = this.mockMvc
|
||||
.perform(
|
||||
post("/notes").contentType(MediaTypes.HAL_JSON).content(
|
||||
this.objectMapper.writeValueAsString(note)))
|
||||
.andExpect(status().isCreated()).andReturn().getResponse()
|
||||
.getHeader("Location");
|
||||
|
||||
this.mockMvc.perform(get(noteLocation)).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("title", is(note.get("title"))))
|
||||
.andExpect(jsonPath("body", is(note.get("body"))))
|
||||
.andExpect(jsonPath("_links.self.href", is(noteLocation)))
|
||||
.andExpect(jsonPath("_links.note-tags", is(notNullValue())));
|
||||
|
||||
Map<String, String> tag = new HashMap<String, String>();
|
||||
tag.put("name", "REST");
|
||||
|
||||
String tagLocation = this.mockMvc
|
||||
.perform(
|
||||
post("/tags").contentType(MediaTypes.HAL_JSON).content(
|
||||
this.objectMapper.writeValueAsString(tag)))
|
||||
.andExpect(status().isCreated()).andReturn().getResponse()
|
||||
.getHeader("Location");
|
||||
|
||||
Map<String, Object> noteUpdate = new HashMap<String, Object>();
|
||||
noteUpdate.put("tags", Arrays.asList(tagLocation));
|
||||
|
||||
document(
|
||||
"note-update-example",
|
||||
this.mockMvc.perform(
|
||||
patch(noteLocation).contentType(MediaTypes.HAL_JSON).content(
|
||||
this.objectMapper.writeValueAsString(noteUpdate)))
|
||||
.andExpect(status().isNoContent()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tagGetExample() throws Exception {
|
||||
Map<String, String> tag = new HashMap<String, String>();
|
||||
tag.put("name", "REST");
|
||||
|
||||
String tagLocation = this.mockMvc
|
||||
.perform(
|
||||
post("/tags").contentType(MediaTypes.HAL_JSON).content(
|
||||
this.objectMapper.writeValueAsString(tag)))
|
||||
.andExpect(status().isCreated()).andReturn().getResponse()
|
||||
.getHeader("Location");
|
||||
|
||||
document("tag-get-example", this.mockMvc.perform(get(tagLocation)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("name", is(tag.get("name"))))
|
||||
.andExpect(jsonPath("_links.self.href", is(tagLocation)))
|
||||
.andExpect(jsonPath("_links.tagged-notes", is(notNullValue())));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tagUpdateExample() throws Exception {
|
||||
Map<String, String> tag = new HashMap<String, String>();
|
||||
tag.put("name", "REST");
|
||||
|
||||
String tagLocation = this.mockMvc
|
||||
.perform(
|
||||
post("/tags").contentType(MediaTypes.HAL_JSON).content(
|
||||
this.objectMapper.writeValueAsString(tag)))
|
||||
.andExpect(status().isCreated()).andReturn().getResponse()
|
||||
.getHeader("Location");
|
||||
|
||||
Map<String, Object> tagUpdate = new HashMap<String, Object>();
|
||||
tagUpdate.put("name", "RESTful");
|
||||
|
||||
document(
|
||||
"tag-update-example",
|
||||
this.mockMvc.perform(
|
||||
patch(tagLocation).contentType(MediaTypes.HAL_JSON).content(
|
||||
this.objectMapper.writeValueAsString(tagUpdate)))
|
||||
.andExpect(status().isNoContent()));
|
||||
}
|
||||
|
||||
private void createNote(String title, String body) {
|
||||
Note note = new Note();
|
||||
note.setTitle(title);
|
||||
note.setBody(body);
|
||||
|
||||
this.noteRepository.save(note);
|
||||
}
|
||||
|
||||
private void createTag(String name) {
|
||||
Tag tag = new Tag();
|
||||
tag.setName(name);
|
||||
this.tagRepository.save(tag);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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 com.example.notes;
|
||||
|
||||
|
||||
abstract class AbstractTagInput {
|
||||
|
||||
private final String name;
|
||||
|
||||
public AbstractTagInput(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -85,6 +85,11 @@ public class NotesController {
|
||||
return httpHeaders;
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
|
||||
void delete(@PathVariable("id") long id) {
|
||||
this.noteRepository.delete(id);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
|
||||
Resource<Note> note(@PathVariable("id") long id) {
|
||||
Note note = this.noteRepository.findById(id).orElseThrow(
|
||||
|
||||
@@ -16,19 +16,15 @@
|
||||
|
||||
package com.example.notes;
|
||||
|
||||
import org.hibernate.validator.constraints.NotBlank;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class TagInput {
|
||||
|
||||
private final String name;
|
||||
public class TagInput extends AbstractTagInput {
|
||||
|
||||
@JsonCreator
|
||||
public TagInput(@JsonProperty("name") String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
public TagInput(@NotBlank @JsonProperty("name") String name) {
|
||||
super(name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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 com.example.notes;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class TagPatchInput extends AbstractTagInput {
|
||||
|
||||
@JsonCreator
|
||||
public TagPatchInput(@NullOrNotBlank @JsonProperty("name") String name) {
|
||||
super(name);
|
||||
}
|
||||
}
|
||||
@@ -72,6 +72,11 @@ public class TagsController {
|
||||
return httpHeaders;
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
|
||||
void delete(@PathVariable("id") long id) {
|
||||
this.repository.delete(id);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
|
||||
Resource<Tag> tag(@PathVariable("id") long id) {
|
||||
Tag tag = this.repository.findById(id).orElseThrow(
|
||||
@@ -86,4 +91,15 @@ public class TagsController {
|
||||
.orElseThrow(() -> new ResourceDoesNotExistException())
|
||||
.getNotes()));
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/{id}", method = RequestMethod.PATCH)
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
void updateTag(@PathVariable("id") long id, @RequestBody TagPatchInput tagInput) {
|
||||
Tag tag = this.repository.findById(id).orElseThrow(
|
||||
() -> new ResourceDoesNotExistException());
|
||||
if (tagInput.getName() != null) {
|
||||
tag.setName(tagInput.getName());
|
||||
}
|
||||
this.repository.save(tag);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user