REST request validation rules for a model - rest

I am writing a REST API and a WEB on top. I would really like the API to provide the WEB with validation and default value information for input models.
Here is a fictional example:
{
"name": "string", // 1 to 50 characters.
"gender": "string", // Must be one of 'Male', 'Female', 'Legal Entity'
"BirthYear": "int" // [1900, 2019] - Default 1999
"weight": "decimal" // numeric(10, 2) Precision=10, Scale=2
"deceased": "bool" // Default = false.
}
I know I can use EnumDataType to list enumerations in Swagger but sometimes I have dynamic enumerations based on values in the database. Gender could for example be dynamic as people identify with new genders all the time :)
So in REST is there a known pattern how to pass such information to the client from the API for example via the OPTION verb?
Can anyone point to a good article or information about something like this?

I think you should validate the type and if the field is not empty, if the genre does not exist in the database, you throws a 422 status code, that indicate a semantical error (the client wanna POST/UPDATE with right media type, a right syntax , but uncorrect semantical value).
The optional method according to RFC2616 is used to view what resource or sever support(allowed methods and headers for example):
The HTTP OPTIONS method is used to describe the communication options
for the target resource.

Related

How to model a progress "resource" in a REST api?

I have the following data structure that contains an array of sectionIds. They are stored in the order in which they were completed:
applicationProgress: ["sectionG", "sectionZ", "sectionA"]
I’d like to be able to do something like:
GET /application-progress - expected: sectionG, sectionZ, sectionA
GET /application-progress?filter[first]=true - expected: sectionG
GET /application-progress?filter[current]=true - expected: sectionA
GET /application-progress?filter[previous]=sectionZ - expected: sectionG
I appreciated the above URLs are incorrect, but I’m not sure how to name/structure them to get the expected data e.g. Are the resources here "sectionids"?
I'd like to adhere to the JSON:API specification.
UPDATE
I'm looking to adhere to JSON:API v1.0
In terms of resources I believe I have "Section" and "ProgressEntry". Each ProgressEntry will have a one-to-one relationship with a Section.
I'd like to be able to query within the collection e.g.
Get the first item in the collection:
GET /progress-entries?filter[first]
Returns:
{
"data": {
"type": "progress-entries",
"id": "progressL",
"attributes": {
"sectionId": "sectionG"
},
"relationships": {
"section": {
"links": {
"related": "http://example.com/sections/sectionG"
}
}
}
},
"included": [
{
"links": {
"self": "http://example.com/sections/sectionG"
},
"type": "sections",
"id": "sectionG",
"attributes": {
"id": "sectionG",
"title": "Some title"
}
}
]
}
Get the previous ProgressEntry given a relative ProgressEntry. So in the following example find a ProgressEntry whose sectionId attribute equals "sectionZ" and then get the previous entry (sectionG). I wasn't clear before that the filtering of this is based on the ProgressEntry's attributes:
GET /progress-entries?filter[attributes][sectionId]=sectionZ&filterAction=getPreviousEntry
Returns:
{
"data": {
"type": "progress-entries",
"id": "progressL",
"attributes": {
"sectionId": "sectionG"
},
"relationships": {
"section": {
"links": {
"related": "http://example.com/sections/sectionG"
}
}
}
},
"included": [
{
"links": {
"self": "http://example.com/sections/sectionG"
},
"type": "sections",
"id": "sectionG",
"attributes": {
"id": "sectionG",
"title": "Some title"
}
}
]
}
I started to comment on jelhan's reply though my answer was just to long for a reasonable comment on his objection, hence I include it here as it more or less provides a good introduction into the answer anyways.
A resource is identified by a unique identifier (URI). A URI is in general independent from any representation format else content-type negotiation would be useless. json-api is a media-type that defines the structure and semantics of representations exchanged for a specific resource. A media-type SHOULD NOT force any constraints on the URI structure of a resource as it is independent from it. One can't deduce the media-type to use based on a given URI even if the URI contains something like vnd.api+json as this might just be a Web page talking about json:api. A client may as well request application/hal+json instead of application/vnd.api+json on the same URI and receive the same state information just packaged in a different representation syntax, if the server supports both representation formats.
Profiles, as mentioned by jelhan, are just extension mechanisms to the actual media-type that allow a general media-type to specialize through adding further constraints, conventions or extensions. Such profiles use URIs similar to XML namespaces, and those URIs NEED NOT but SHOULD BE de-referencable to allow access to further documentation. There is no talk about the URI of the actual resource other than given by Web Linking that URIs may hint a client on the media-type to use, which I would not recommend as this requires a client to have certain knowledge about that hint.
As mentioned in my initial comments, URIs shouldn't convey semantics as link relations are there for!
Link-relations
By that, your outlined resource seems to be a collection of some further resources, sections by your domain language. While pagination as defined in json:api does not directly map here perfectly, unless you have so many sections that you want to split these into multiple pages, the same concept can be used using standardized link relations defined by IANA.
Here, at one point a server may provide you a link to the collection resource which may look like this:
{
"links": {
"self": "https://api.acme.org/section-queue",
"collection": "https://api.acme.org/app-progression",
...
},
...
}
Due to the collection link relation standardized by IANA you know that this resource may hold a collection of entries which upon invoking may return a json:api representation such as:
{
"links": {
"self": "https://api.acme.org/app-progression",
"first": "https://api.acme.org/app-progression/sectionG",
"last": "https://api/acme.org/app-progression/sectionA",
"current": "https://api.acme.org/app-progression",
"up": "https://api.acme.org/section-queue",
"https://api/acme.org/rel/section": "https://api.acme.org/app-progression/sectionG",
"https://api/acme.org/rel/section": "https://api.acme.org/app-progression/sectionZ",
"https://api/acme.org/rel/section": "https://api.acme.org/app-progression/sectionA",
...
},
...
}
where you have further links to go up or down the hierarchy or select the first or last section that finished. Note the last 3 sample URIs that leverages the extension relation types mechanism defined by RFC 5988 (Web Linking).
On drilling down the hierarchy further you might find links such as
{
"links": {
"self": "https://api.acme.org/app-progression/sectionZ",
"first": "https://api.acme.org/app-progression/sectionG",
"prev": "https://api.acme.org/app-progression/sectionG",
"next": "https://api.acme.org/app-progression/sectionA",
"last": "https://api.acme.org/app-progression/sectionA",
"current": "https://api.acme.org/app-progression/sectionA",
"up": "https://api.acme.org/app-progression",
...
},
...
}
This example should just showcase how a server is providing you with all the options a client may need to progress through its task. It will simply follow the links it is interested in. Based on the link relation names provided a client can make informed choices on whether the provided link is of interest or not. If it i.e. knows that a resource is a collection it might to traverse through all the elements and processes them one by one (or by multiple threads concurrently).
This approach is quite common on the Internet and allows the server to easily change its URI scheme over time as clients will only act upon the link relation name and only invoke the URI without attempting to deduce any logic from it. This technique is also easily usable for other media-types such as application/hal+json or the like and allows each of the respective resources to be cached and reused by default, which might take away load from your server, depending on the amount of queries it has to deal with.
Note that no word on the actual content of that section was yet said. It might be a complex summary of things typical to sections or it might just be a word. We could classify it and give it a name, as such even a simple word is a valid target for a resource. Further, as Jim Webber mentioned, your resources you expose via HTTP (REST) and your domain model are not identical and usually do not map one-to-one.
Filtering
json:api allows to group parameters together semantically by defining a customized x-www-form-urlencoded parsing. If content-type negotiation is used to agree on json:api as representation format, the likelihood of interoperability issues is rather low, though if such a representation is sent unsolicitedly parsing of such query parameters might fail.
It is important to mention that in a REST architecture clients should only use links provided by the server and not generate one on their own. A client usually is not interested in the URI but in the content of the URI, hence the server needs to know how to act upon the URI.
The outlined suggestions can be used but also URIs of the form
.../application-progress?filter=first
.../application-progress?filter=current
.../application-progress?filter=previous&on=sectionZ
can be used instead. This approach should in addition to that also work on almost all clients without the need to change their url-encoded parsing mechanism. In addition to that he management overhead to return URIs for other media-types generated may be minimized as well. Note that each of the URIs in the example above represent their own resource and a cache will store responses to such resources based on the URI used to retrieve such results. Queries like .../application-progress?filter=next&on=sectionG and .../application-progress?filter=previous&on=sectionA which retrieve basically the same representations are two distinctive resources which will be processed two times by your API as the response of the first query can't be reused as the cache key (URI) is different. According to Fielding caching is one of the few constraints REST has which has to be respected otherwise you are violating it.
How you design such URIs is completely up to you here. The important thing is, how you teach a client when to invoke such URIs and when it should not. Here, again, link-relations can and should be used.
Summary
In summary, which approach you prefer is up to you as well as which URI style you choose. Clients, especially in a REST environment, do not care about the structure of the URI. They operate on link-relations and use the URI just for invoking it to progress on with their task. As such, a server API should help a client by teaching it what it needs to know like in a text-based computer game in the 70/80's as mentioned by Jim Webber. It is helpful to think of the interaction model to design as affordances and state machine as explained by Asbjørn Ulsberg .
While you could apply filtering on grouped parameters provided by json:api such links may only be usable within the `json:api´ representation. If you copy & paste such a link to a browser or to some other channel, it might not be processable by that client. Therefore this would not be my first choice, TBH. Whether or not you design sections to be their own resource or just properties you want to retrieve is your choice here as well. We don't know really what sections are in your domain model, IMO it sounds like a valid resource though that may or may not have further properties.

REST API - How to query for links discovery?

Suppose I have a RESTful HATEOAS API which has /posts endpoint which lists posts with a query shortcut /posts/new. How do I query the API to discover /posts/new?
My ideas:
1) Query /posts and get links from _links attribute (and the entities listed are necessary overhead):
GET /posts
{
"docs": [
...
]
"_links": {
"new": { "rel": "posts", "href": "/posts/new" }
}
}
2) Provide this in the API root together with list of resources:
GET /
{
"resources": {
"posts": {
"_links": {
"self": { "rel": "posts", "href": "/posts" }
"new": { "rel": "posts", "href": "/posts/new" }
}
}
}
}
3) I should not use the /posts/new query and instead use /posts and query params. However, if I change my server logic I would have to change client logic too and that would be serve-client coupling. For example:
New messages will be requested by client by somehow providing parameter timestamp > (today - 30)
I introduce draft property and change my idea that new are only the posts with timestamp > (today - 30) && draft = false
I have to change client to add drafts constraint
Note: posts is just an example I am asking in general.
In a REST architecture URIs should be discovered via their accompanying link-relation name. On interpreting your examples above as HAL the URI /post/new has a link-relation name of new. Link relation names provide semantics to URIs which allow clients to determine when to invoke these URIs. HAL is just one of a handful JSON-based media types that support HATEOAS. There are further media-types available that provide a similar job with slightly different syntax and capabilities.
Upon receiving such a document a client would parse the message and build some context for the message containing the actual content including additional metadata like links and further embedded data. If it wants to retrieve the list of the most recent posts it basically needs to look up the key (link-relation name) that expresses the intent (new in your case) from the before-mentioned context in order to retrieve the assigned value (URI). How a client maintains this context is some implementation detail. It might build up a tree-map for easier lookup of "link-relation" keys and their values (URIs) or use some totally different approach.
The knowledge what key to use needs to be present somehow. As link relations express certain semantics they need to be specified somewhere. This can happen in industry standards or media-type definitions. IANA maintains a list of standardized link-relation names and their semantics. On examining the list probably the most likely match according to your specification is current which is defined as
Refers to a resource containing the most recent item(s) in a collection of resources.
I'd therefore propose to change the link-relation name from new to current.
Well, the whole point of the RESTFUL is to make the links discovery easy by making them correspond to the HTTP method used by the client. That means all your links would be simply named /post, the only thing that would change is the htpp method and the parameters they take, which your server would use to determine the actual operation the client wants.
This is a sample from a C# project (notice that the links are all the same, the only changes are the HTTP_METHOD and/or the parameter passed):
List of common http methods: POST, GET, PUT, DELETE

Can REST URLs send back UI rendering hints as metadata?

I could not find a definitive document mentioning if UI rendering hints can be sent back as REST metadata or not. First of all, what all can be classified as REST metadata? Surely, data type of attribute can be metadata but can presentation hints like whether an attribute is single-valued or multi-valued also be described metadata? What about "ishidden" and "isReadOnly"? As per my understanding information like min/max/regexp/fixed value are OK as metadata but not sure if anything related to presentation hints like the one I mentioned above are good candidates for REST metadata? Any pointers would be of utmost help.
Thanks,
Paddy
Honnestly you are free to send back to the REST client what you want in your payload. However I'm not sure that it's a good idea to always use these UI-oriented metadata. As a matter of fact, you can also have applications that consume data without having UI issues.
You could implement a mechanism that let you select the metadata level you want to return to the REST client within the content negociation (Conneg based on the header Accept) in a similar way that what OData does. Here is a sample below:
GET serviceRoot/People
Accept: application/json;odata.metadata=minimal
You could imagine the following values for the header Accept:
No metadata: application/json;metadata=none
Structural metadata (property type, ...): application/json;metadata=minimal
Validation metadata (useful to determine the expected values for properties): application/json;metadata=validation
UI rendering metadata (readonly, ...): application/json;metadata=rendering
You can then structure the content as described below:
{
"property1": "value",
// Structural
"property1#metadata": {
"type": "string"
},
"property2": 10,
// Structural + validation
"property2#metadata": {
"type": "integer"
"minValue": 2,
"maxValue": 15
},
"property3": 10,
// Structural + ui rendering
"property3#metadata": {
"type": "integer"
"minValue": 2,
"maxValue": 15,
"readOnly": true,
"hidden": false
}
}
If you want to have a look at how metadata are handled within OData v4, you can use the following links from odata.org:
Basic tutorial - http://www.odata.org/getting-started/basic-tutorial/
Advanced tutorial - http://www.odata.org/getting-started/advanced-tutorial/
EDIT: in a comment, inf3rno underlines the header Prefer could also be used to describe the meta level required.
Here is a sample of use below:
GET serviceRoot/People
Accept: application/json
Prefer: metadata=rendering
Hope it helps you,
Thierry

Breeze failing silently while parsing metadata

Trying to get going on breeze but encountering the worst kind of error, which is none at all. It appears the metadata that I am producing is not being accepted by breeze. I know currently there are some issues with the metadata, such as 'foreignKeyNamesOnServer' has incorrect values in it and a bunch of others. The metadata I am producing can be viewed here (too large):
http://pastebin.com/ycP4jXxn
var serviceName = 'http://www.dockyard.com:8080/rest';
var entityManager = new breeze.EntityManager({serviceName: serviceName});
var entityQuery = new breeze.EntityQuery();
var query = breeze.EntityQuery.from("application")
entityManager.executeQuery(query)
.then(function (data) {
console.log(data);
}, function (error) {
console.log(error);
});
So the behaviour I am seeing is no javascript errors related to metadata parsing, the metadata is returning ok with 200 OK. The hit to /rest/application is returning 200 OK with the following data.
[{"#id":1,"id":1,"name":"dsad","deploymentStrategies":null,"versions":null,"groups":null},{"#id":2,"id":2,"name":"sss","deploymentStrategies":null,"versions":null,"groups":null},{"#id":3,"id":3,"name":"fdsfs","deploymentStrategies":null,"versions":null,"groups":null},{"#id":4,"id":4,"name":"fdsa","deploymentStrategies":null,"versions":null,"groups":null},{"#id":5,"id":5,"name":"dasda","deploymentStrategies":null,"versions":null,"groups":null}]
Promise is calling the error callback with: cannot execute _executeQueryCore until metadataStore is populated
The contents of the metadata store:
{"namingConvention":{"name":"camelCase"},"localQueryComparisonOptions":{"name":"caseInsensitiveSQL","isCaseSensitive":false,"usesSql92CompliantStringComparison":true},"dataServices":[{"serviceName":"http://www.dockyard.com:8080/rest/","hasServerMetadata":true,"jsonResultsAdapter":"webApi_default","useJsonp":false}],"_resourceEntityTypeMap":{"platform":"Platform:#com.psidox.dockyard.controller.model.dockyard","application":"Application:#com.psidox.dockyard.controller.model.application","host":"Host:#com.psidox.dockyard.controller.model.host","groupdeploymentstrategy":"GroupDeploymentStrategy:#com.psidox.dockyard.controller.model.application","dockyard":"Dockyard:#com.psidox.dockyard.controller.model.dockyard","configurationentry":"ConfigurationEntry:#com.psidox.dockyard.controller.model","hoststrategy":"HostStrategy:#com.psidox.dockyard.controller.model.application","dockerimage":"DockerImage:#com.psidox.dockyard.controller.model.docker","version":"Version:#com.psidox.dockyard.controller.model.application","docker":"Docker:#com.psidox.dockyard.controller.model.docker","hostproviderconfig":"HostProviderConfig:#com.psidox.dockyard.controller.model.host","hostprovider":"HostProvider:#com.psidox.dockyard.controller.model.host","metadataimpl":"MetadataImpl:#com.psidox.dockyard.controller.model","deployment":"Deployment:#com.psidox.dockyard.controller.model.dockyard","hosttype":"HostType:#com.psidox.dockyard.controller.model.host","group":"Group:#com.psidox.dockyard.controller.model.application","groupimplementation":"GroupImplementation:#com.psidox.dockyard.controller.model.application","deploymentstrategy":"DeploymentStrategy:#com.psidox.dockyard.controller.model.dockyard","groupdeployment":"GroupDeployment:#com.psidox.dockyard.controller.model.application","metadata":"Metadata:#com.psidox.dockyard.controller.model"},"_structuralTypeMap":{},"_shortNameMap":{},"_ctorRegistry":{},"_incompleteTypeMap":{},"_incompleteComplexTypeMap":{},"_id":0,"_deferredTypes":{}}"
I am pretty sure this error is related to Metadata store not being populated correctly from my metadata. Just wondering why Breeze is not throwing any type of error when it is encountering invalid metadata?
Edit:
After debugging the parse metadata call it appears that Breeze Metadata Schema Documentation is out of date. At a quick glance this is what it looks like has changed:
Key name "structuralTypeMap" has changed to "structuralTypes".
"structuralTypeMap" use to be a object with the key as the EntityTypeName and value was the Entity definition. Now it appears that "structuralTypes" is an array with the Entity definitions.
Suggestions also there should possibly be an exception thrown if the metadata doesn't contain any structuralTypes? Currently it is failing silently which isn't very helpful for debugging.
I fear they you've jumped into the deep end of the pool before learning to swim. I admire your bravery but I'm not surprised that you're struggling to stay afloat. You're not following any of the easy paths we've set out for you. I assume that is because none of these paths are suitable to your situation.
On the bright side, you've reinforced by sense that we soon must make it easier for developers who get their data from a custom REST service.
Problem #1
The Query results do not identify the EntityType and you didn't mention that you wrote a custom JsonResultsAdapter to cope with that. Your question and your MetadataStore contents below suggest that you are using the out-of-the-box Web API adapter which wouldn't know what to do with the JSON query results.
Here is one item in the JSON payload from your query, reformatted for readability
{
"#id": 1,
"id": 1,
"name": "dsad",
"deploymentStrategies": null,
"versions": null,
"groups": null
}
There's nothing in there to indicate to which EntityType this data belongs. Just looking I have no idea what type this is. Breeze won't know either.
You'll need to learn about the "JsonResultsAdapter" which is how Breeze interprets JSON data arriving from the server and maps it into instances of EntityTypes.
The Ruby Sample has a custom JsonResultsAdapter. It depends upon the fact that the server is explicit about the type of each object it returns; see how every Rails view adds a $type node (for example, the sessions:index view). This is the approach to take if you can control what the server sends the client.
The Edmunds Sample has a custom JsonResultsAdapter that has to infer the type by examining the characteristics of the JSON data. It's kind of a forensic exercise that you only want to indulge if you have to do so.
Problem #2
The MetadataStore you serialized is empty of all type information. Here it is reformatted for legibility
{
"namingConvention": {
"name": "camelCase"
},
"localQueryComparisonOptions": {
"name": "caseInsensitiveSQL",
"isCaseSensitive": false,
"usesSql92CompliantStringComparison": true
},
"dataServices": [
{
"serviceName": "http:\/\/www.dockyard.com:8080\/rest\/",
"hasServerMetadata": true,
"jsonResultsAdapter": "webApi_default",
"useJsonp": false
}
],
"_resourceEntityTypeMap": {
"platform": "Platform:#com.psidox.dockyard.controller.model.dockyard",
"application": "Application:#com.psidox.dockyard.controller.model.application",
... a bunch more ...
},
"_structuralTypeMap": {
},
"_shortNameMap": {
},
... more emptiness ...
}
}
I'm not really surprised, having discovered problem #3.
Problem #3
Your raw metadata doesn't match a format that Breeze understands. It looks like you cobbled it together by hand. It sure doesn't look like anything I recognize. It doesn't match the CSDL format from Entity Framework. It doesn't match the "Breeze Metadata Format" that you'd see when you exported a MetadataStore.
It's in trouble almost immediately. Here is how you start the definition of your first type:
"structuralTypeMap": {
"Group:#com.psidox.dockyard.controller.model.application": {
"shortName": "Group",
"namespace": "com.psidox.dockyard.controller.model.application",
Here is how it should begin:
"structuralTypes": [
{
"shortName": "Group",
"namespace": "com.psidox.dockyard.controller.model.application",
I accept your point that the Breeze Metadata Schema Documentation is incorrect. We should fix that.
I'm sympathetic with your argument that Breeze should have thrown an exception. I can see why it didn't throw. It simply ignored all the nodes that it didn't understand. A lot of parsers do that, not that that is a sufficient excuse.
In this case, it ignored the "structuralTypeMap" node and everything it had to say about types. When the parser was done, it had learned nothing at all about the types. Breeze can't know how many types you'll specify but it could act suspicious if there are none.
I confess I personally never thought to use this metadata schema description as my guide. That would be just about the hardest possible way to write metadata.
I suggest that you look at the documentation topic "Metadata by hand".
In sum
Please examine a simple example first. Maybe Edmunds. Maybe Ruby.
Learn to write metadata by hand; it's not hard.
Learn about the JsonResultsAdapter
We do hope soon to offer specific guidance for the developer who has a "vanilla" REST data service.

Best practice for partial updates in a RESTful service

I am writing a RESTful service for a customer management system and I am trying to find the best practice for updating records partially. For example, I want the caller to be able to read the full record with a GET request. But for updating it only certain operations on the record are allowed, like change the status from ENABLED to DISABLED. (I have more complex scenarios than this)
I don't want the caller to submit the entire record with just the updated field for security reasons (it also feels like overkill).
Is there a recommended way of constructing the URIs? When reading the REST books RPC style calls seem to be frowned upon.
If the following call returns the full customer record for the customer with the id 123
GET /customer/123
<customer>
{lots of attributes}
<status>ENABLED</status>
{even more attributes}
</customer>
how should I update the status?
POST /customer/123/status
<status>DISABLED</status>
POST /customer/123/changeStatus
DISABLED
...
Update: To augment the question. How does one incorporate 'business logic calls' into a REST api? Is there an agreed way of doing this? Not all of the methods are CRUD by nature. Some are more complex, like 'sendEmailToCustomer(123)', 'mergeCustomers(123, 456)', 'countCustomers()'
POST /customer/123?cmd=sendEmail
POST /cmd/sendEmail?customerId=123
GET /customer/count
You basically have two options:
Use PATCH (but note that you have to define your own media type that specifies what will happen exactly)
Use POST to a sub resource and return 303 See Other with the Location header pointing to the main resource. The intention of the 303 is to tell the client: "I have performed your POST and the effect was that some other resource was updated. See Location header for which resource that was." POST/303 is intended for iterative additions to a resources to build up the state of some main resource and it is a perfect fit for partial updates.
You should use POST for partial updates.
To update fields for customer 123, make a POST to /customer/123.
If you want to update just the status, you could also PUT to /customer/123/status.
Generally, GET requests should not have any side effects, and PUT is for writing/replacing the entire resource.
This follows directly from HTTP, as seen here: http://en.wikipedia.org/wiki/HTTP_PUT#Request_methods
You should use PATCH for partial updates - either using json-patch documents (see https://datatracker.ietf.org/doc/html/draft-ietf-appsawg-json-patch-08 or http://www.mnot.net/blog/2012/09/05/patch) or the XML patch framework (see https://www.rfc-editor.org/rfc/rfc5261). In my opinion though, json-patch is the best fit for your kind of business data.
PATCH with JSON/XML patch documents has very strait forward semantics for partial updates. If you start using POST, with modified copies of the original document, for partial updates you soon run into problems where you want missing values (or, rather, null values) to represent either "ignore this property" or "set this property to the empty value" - and that leads down a rabbit hole of hacked solutions that in the end will result in your own kind of patch format.
You can find a more in-depth answer here: http://soabits.blogspot.dk/2013/01/http-put-patch-or-post-partial-updates.html.
I am running into a similar problem. PUT on a sub-resource seems to work when you want to update only a single field. However, sometimes you want to update a bunch of things: Think of a web form representing the resource with option to change some entries. The user's submission of form should not result in a multiple PUTs.
Here are two solution that I can think of:
do a PUT with the entire resource. On the server-side, define the semantics that a PUT with the entire resource ignores all the values that haven't changed.
do a PUT with a partial resource. On the server-side, define the semantics of this to be a merge.
2 is just a bandwidth-optimization of 1. Sometimes 1 is the only option if the resource defines some fields are required fields (think proto buffers).
The problem with both these approaches is how to clear a field. You will have to define a special null value (especially for proto buffers since null values are not defined for proto buffers) that will cause clearing of the field.
Comments?
RFC 7396: JSON Merge Patch (published four years after the question was posted) describes the best practices for a PATCH in terms of the format and processing rules.
In a nutshell, you submit an HTTP PATCH to a target resource with the application/merge-patch+json MIME media type and a body representing only the parts that you want to be changed/added/removed and then follow the below processing rules.
Rules:
If the provided merge patch contains members that do not appear within the target, those members are added.
If the target does contain the member, the value is replaced.
Null values in the merge patch are given special meaning to indicate the removal of existing values in the target.
Example test cases that illustrate the rules above (as seen in the appendix of that RFC):
ORIGINAL PATCH RESULT
--------------------------------------------
{"a":"b"} {"a":"c"} {"a":"c"}
{"a":"b"} {"b":"c"} {"a":"b",
"b":"c"}
{"a":"b"} {"a":null} {}
{"a":"b", {"a":null} {"b":"c"}
"b":"c"}
{"a":["b"]} {"a":"c"} {"a":"c"}
{"a":"c"} {"a":["b"]} {"a":["b"]}
{"a": { {"a": { {"a": {
"b": "c"} "b": "d", "b": "d"
} "c": null} }
} }
{"a": [ {"a": [1]} {"a": [1]}
{"b":"c"}
]
}
["a","b"] ["c","d"] ["c","d"]
{"a":"b"} ["c"] ["c"]
{"a":"foo"} null null
{"a":"foo"} "bar" "bar"
{"e":null} {"a":1} {"e":null,
"a":1}
[1,2] {"a":"b", {"a":"b"}
"c":null}
{} {"a": {"a":
{"bb": {"bb":
{"ccc": {}}}
null}}}
For modifying the status I think a RESTful approach is to use a logical sub-resource which describes the status of the resources. This IMO is pretty useful and clean when you have a reduced set of statuses. It makes your API more expressive without forcing the existing operations for your customer resource.
Example:
POST /customer/active <-- Providing entity in the body a new customer
{
... // attributes here except status
}
The POST service should return the newly created customer with the id:
{
id:123,
... // the other fields here
}
The GET for the created resource would use the resource location:
GET /customer/123/active
A GET /customer/123/inactive should return 404
For the PUT operation, without providing a Json entity it will just update the status
PUT /customer/123/inactive <-- Deactivating an existing customer
Providing an entity will allow you to update the contents of the customer and update the status at the same time.
PUT /customer/123/inactive
{
... // entity fields here except id and status
}
You are creating a conceptual sub-resource for your customer resource. It is also consistent with Roy Fielding's definition of a resource: "...A resource is a conceptual mapping to a set of entities, not the entity that corresponds to the mapping at any particular point in time..." In this case the conceptual mapping is active-customer to customer with status=ACTIVE.
Read operation:
GET /customer/123/active
GET /customer/123/inactive
If you make those calls one right after the other one of them must return status 404, the successful output may not include the status as it is implicit. Of course you can still use GET /customer/123?status=ACTIVE|INACTIVE to query the customer resource directly.
The DELETE operation is interesting as the semantics can be confusing. But you have the option of not publishing that operation for this conceptual resource, or use it in accordance with your business logic.
DELETE /customer/123/active
That one can take your customer to a DELETED/DISABLED status or to the opposite status (ACTIVE/INACTIVE).
Things to add to your augmented question. I think you can often perfectly design more complicated business actions. But you have to give away the method/procedure style of thinking and think more in resources and verbs.
mail sendings
POST /customers/123/mails
payload:
{from: x#x.com, subject: "foo", to: y#y.com}
The implementation of this resource + POST would then send out the mail. if necessary you could then offer something like /customer/123/outbox and then offer resource links to /customer/mails/{mailId}.
customer count
You could handle it like a search resource (including search metadata with paging and num-found info, which gives you the count of customers).
GET /customers
response payload:
{numFound: 1234, paging: {self:..., next:..., previous:...} customer: { ...} ....}
Use PUT for updating incomplete/partial resource.
You can accept jObject as parameter and parse its value to update the resource.
Below is the Java function which you can use as a reference :
public IHttpActionResult Put(int id, JObject partialObject) {
Dictionary < string, string > dictionaryObject = new Dictionary < string, string > ();
foreach(JProperty property in json.Properties()) {
dictionaryObject.Add(property.Name.ToString(), property.Value.ToString());
}
int id = Convert.ToInt32(dictionaryObject["id"]);
DateTime startTime = Convert.ToDateTime(orderInsert["AppointmentDateTime"]);
Boolean isGroup = Convert.ToBoolean(dictionaryObject["IsGroup"]);
//Call function to update resource
update(id, startTime, isGroup);
return Ok(appointmentModelList);
}
Check out http://www.odata.org/
It defines the MERGE method, so in your case it would be something like this:
MERGE /customer/123
<customer>
<status>DISABLED</status>
</customer>
Only the status property is updated and the other values are preserved.
Regarding your Update.
The concept of CRUD I believe has caused some confusion regarding API design. CRUD is a general low level concept for basic operations to perform on data, and HTTP verbs are just request methods (created 21 years ago) that may or may not map to a CRUD operation. In fact, try to find the presence of the CRUD acronym in the HTTP 1.0/1.1 specification.
A very well explained guide that applies a pragmatic convention can be found in the Google cloud platform API documentation. It describes the concepts behind the creation of a resource based API, one that emphasizes a big amount of resources over operations, and includes the use cases that you are describing. Although is a just a convention design for their product, I think it makes a lot of sense.
The base concept here (and one that produces a lot of confusion) is the mapping between "methods" and HTTP verbs. One thing is to define what "operations" (methods) your API will do over which types of resources (for example, get a list of customers, or send an email), and another are the HTTP verbs. There must be a definition of both, the methods and the verbs that you plan to use and a mapping between them.
It also says that, when an operation does not map exactly with a standard method (List, Get, Create, Update, Delete in this case), one may use "Custom methods", like BatchGet, which retrieves several objects based on several object id input, or SendEmail.
It doesn't matter. In terms of REST, you can't do a GET, because it's not cacheable, but it doesn't matter if you use POST or PATCH or PUT or whatever, and it doesn't matter what the URL looks like. If you're doing REST, what matters is that when you get a representation of your resource from the server, that representation is able give the client state transition options.
If your GET response had state transitions, the client just needs to know how to read them, and the server can change them if needed. Here an update is done using POST, but if it was changed to PATCH, or if the URL changes, the client still knows how to make an update:
{
"customer" :
{
},
"operations":
[
"update" :
{
"method": "POST",
"href": "https://server/customer/123/"
}]
}
You could go as far as to list required/optional parameters for the client to give back to you. It depends on the application.
As far as business operations, that might be a different resource linked to from the customer resource. If you want to send an email to the customer, maybe that service is it's own resource that you can POST to, so you might include the following operation in the customer resource:
"email":
{
"method": "POST",
"href": "http://server/emailservice/send?customer=1234"
}
Some good videos, and example of the presenter's REST architecture are these. Stormpath only uses GET/POST/DELETE, which is fine since REST has nothing to do with what operations you use or how URLs should look (except GETs should be cacheable):
https://www.youtube.com/watch?v=pspy1H6A3FM,
https://www.youtube.com/watch?v=5WXYw4J4QOU,
http://docs.stormpath.com/rest/quickstart/