In rails there's an easy way to add a collection end point to routes. e.g.
resources :books do
member do
get 'publisher' # /books/id/publisher
end
collection do
get 'total_count' # /books/total_count
end
end
Is there a similar way to map the total_count endpoint in Grails? The example here ( http://grails.org/doc/2.3.1/guide/single.html#urlMappings ) only shows a member route.
"/books"(resources: "book") {
"/publisher"(controller:"publisher")
"/total_count"(controller: "publisher") // ??? can this work?
}
I am currently using Grails 2.3.4.
It was simpler than I thought though if there's a more canonical way of solving this I'd appreciate the feedback.
Basically I defined the collection endpoint before the resource endpoint.
class UrlMappings {
static mappings = {
"/books/total_count" (controller: "Book", action: "totalCount", method: "GET")
"/books" (resources: "Book")
}
}
So far it appears to be working.
Related
We are looking at using Slim 3 as the framework for our API. I have searched SO and the Slim docs, but cannot find an answer to the issue. If we have different route files (e.g. v1, v2, etc.) and if two routes have the same signature, an error is thrown. Is there any way to cascade the routes so that the last loaded route for a particular signature is used?
For example, say v1.php has a route for GET ("/test") and v2.php also contains this route, can we use the latest version? Even simpler would be if a file of routes contains two routes with the same signature, is there a way of the latter method being used (and no error being thrown)?
A similar question is asked here but this uses hooks (which have been removed from Slim 3 as per here)
I looked at the Slim code and I didn't find a simple way of allowing duplicated routes (preventing the exception).
The new Slim uses FastRoute as dependency. It calls FastRoute\simpleDispatcher and doesn't offer any configuration possiblity. Even if it did allow some configuration, FastRoute doesn't have any built-in option to allow duplicated routes. A custom implementation of a DataGenerator would be needed.
But following the instructions above, we can get a custom DataGenerator by passing to Slim App a custom Router which instantiates some FastRoute::Dispatcher implementation which then uses the custom DataGenerator.
First the CustomDataGenerator (let's go the easy way and do some copy and pasting from \FastRoute\RegexBasedAbstract and \FastRoute\GroupCountBased)
<?php
class CustomDataGenerator implements \FastRoute\DataGenerator {
/*
* 1. Copy over everything from the RegexBasedAbstract
* 2. Replace abstract methods with implementations from GroupCountBased
* 3. change the addStaticRoute and addVariableRoute
* to the following implementations
*/
private function addStaticRoute($httpMethod, $routeData, $handler) {
$routeStr = $routeData[0];
if (isset($this->methodToRegexToRoutesMap[$httpMethod])) {
foreach ($this->methodToRegexToRoutesMap[$httpMethod] as $route) {
if ($route->matches($routeStr)) {
throw new BadRouteException(sprintf(
'Static route "%s" is shadowed by previously defined variable route "%s" for method "%s"',
$routeStr, $route->regex, $httpMethod
));
}
}
}
if (isset($this->staticRoutes[$httpMethod][$routeStr])) {
unset($this->staticRoutes[$httpMethod][$routeStr]);
}
$this->staticRoutes[$httpMethod][$routeStr] = $handler;
}
private function addVariableRoute($httpMethod, $routeData, $handler) {
list($regex, $variables) = $this->buildRegexForRoute($routeData);
if (isset($this->methodToRegexToRoutesMap[$httpMethod][$regex])) {
unset($this->methodToRegexToRoutesMap[$httpMethod][$regex]);
}
$this->methodToRegexToRoutesMap[$httpMethod][$regex] = new \FastRoute\Route(
$httpMethod, $handler, $regex, $variables
);
}
}
Then the custom Router
<?php
class CustomRouter extends \Slim\Router {
protected function createDispatcher() {
return $this->dispatcher ?: \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r) {
foreach ($this->getRoutes() as $route) {
$r->addRoute($route->getMethods(), $route->getPattern(), $route->getIdentifier());
}
}, [
'routeParser' => $this->routeParser,
'dataGenerator' => new CustomDataGenerator()
]);
}
}
and finally instantiate the Slim app with the custom router
<?php
$app = new \Slim\App(array(
'router' => new CustomRouter()
));
The code above, if a duplicated route is detected, removes the previous route and stores the new one.
I hope I didn't miss any simpler way of achieving this result.
We got a Web API webservice with entity framework and accept JSON calls.
We have a call named: GetResidents which lists all residents. We would like to have an extra parameter (hash) which allows the caller to filter the results on the server.
Like this:
{"filter":{
"and":{
"age":{
"less_than":80,
"greater_than":60
}
},
{
"active":{
"eq":true
}
}
In RoR in the past I've used this gem which works great: https://github.com/QutBioacoustics/baw-server/wiki/Rails-API-Spec:-Filtering Does something similar exist in WebAPI?
Thanks for any feedback.
Use OData. Here is documentation link. Basic example:
public class ResidentsController : ApiController
{
[Queryable]
public IQueryable<Resident> GetResidents() {}
}
For your json:
http://localhost/api/residents?$filter=age lt 80 and age gt 60 and active eq true
I understand and have gotten RESTful routes working in my application using this guide http://docs.cherrypy.org/dev/progguide/REST.html
Does anyone know how to add a second RESTful resource nested within a first?
I'm expecting my code to look something like this, but I can't get it to work
import cherrypy
class Pets:
exposed = True
def GET(self, personID, petID):
pass # GET /people/123/pets/333 return pet
def POST(self, personID):
pass # POST /people/123/pets create pet
class People:
pets = Pets()
exposed = True
def GET(self, personID):
pass # GET /people/123 return person
def POST(self):
pass # POST /people create person
config = {
'/people': {
'request.dispatch': cherrypy.dispatch.MethodDispatcher()
}
}
cherrypy.tree.mount(.., '/', config)
See the help docs for cherrypy.popargs. It pops path components, and supplies them as keyword arguments to the next handler. In this case, use it as a decorator on the people resource, and attach a pets resource to the people resource.
#cherrypy.popargs('petID')
class Pets:
...
#cherrypy.popargs('personID')
class People:
...
When exposing querystring parameters using GET I have the following base URL:
https://school.service.com/api/students
This will return the first 25 students.
What if I want to return a list of students based on ONE of the following criteria:
* have accepted a job
* have received a job offer
* have no job offers
The three above choices are essentially an enum.
Therefore, the query request for students who have no job offers I assume would look like:
https://school.service.com/api/students?jobOfferStatus=3
However, I'm wondering if jobOfferStatus=3 is the proper way to handle this. If so, how would I publish/provide to the clients a list of available options for that jobOfferStatus query parameter? What about other possible query parameters and their valid options? We'll have many possible query parameters like this.
I'd love to see an example of how this should be done properly. What are the best practices?
There are two main options: documenting it, or making it discoverable. A lot of APIs have documentation where they list all of the resources and parameters for reference. Otherwise, the client won't know.
You could also make it discoverable in some way by including the options in a response. For conventions on this, search for HATEOAS if you haven't already. (I'm not really knowledgeable enough about HATEOAS myself to make a suggestion.)
I will mention that "3" is not a very meaningful value for jobOfferStatus, and there's no need for the client to know that number. You can make it anything you want -- jobOfferStatus=none or even jobOffer=none. Your controller can do the work of matching that value to your enumeration. Try to design your interface to be intuitive for developers (and, of course, write good documentation).
To handle multiple query parameters, you can use optional parameters in your function:
public HttpResponseMessage GetStudents(string jobOffer = "",
string other1 = "",
string other2 = "")
{
if (jobOffer == "accepted" && other2 == "whatever") {
// return a response
}
else {
// return a different response
}
}
When the client uses parameters by those names, you can tailor your response appropriately.
You have some options to do this, let's try to help:
1) Configure a generic route to asp.net web api knows how to solve another action's name different from Get to a get method, on the App_Start\WebConfigApi.cs class, try to add this:
config.Routes.MapHttpRoute("DefaultApiWithActionAndId",
"api/{controller}/{action}/{id}",
new { id = RouteParameter.Optional });
Using it, you can have diferent methods on the api controller:
// request: get
// url: api/Students/GetStudents
public HttpResponseMessage GetStudents()
{
return Request.CreateResponse(...);
}
// request: get
// url: api/Students/GetStudentsWithJobOffer
public HttpResponseMessage GetStudentsWithJobOffer()
{
return Request.CreateResponse(...);
}
// request: get
// url: api/Students/GetStudentsAcceptedJob
public HttpResponseMessage GetStudentsAcceptedJob()
{
return Request.CreateResponse(...);
}
2) Use a simple parameter on the Get method:
// request: get
// url: api/Students?jobOfferStatus=1
public HttpResponseMessage GetStudents(int jobOfferStatus)
{
// use jobOfferStatus parameter to fill some list
return Request.CreateResponse(...);
}
3) Use a simple method with a parameter named id, to get a default friendly url by asp.net mvc web api.
// request: get
// url: api/Students/1
public HttpResponseMessage GetStudents(int id)
{
// use the id parameter to fill some list
return Request.CreateResponse(...);
}
I've recently started a project using Luracast Restler. It seems a very simple and effective way to set up a REST API. With very little code, I was able to provide CRUD services for my Category and Product resources.
My GET methods look like this:
class Categories
{
function get($id=NULL) {
if (isset($id))
{
// return category details for $id.
}
else
{
// return all categories.
}
}
}
class Products
{
function get($id=NULL) {
if (isset($id))
{
// return product details for $id.
}
else
{
// return all products.
}
}
}
Clients can get the details of the "books" category using:
http:api/categories/books
or all categories using:
http:api/categories
Same for products. One product:
http:api/products/123
All products:
http:api/products
So far so good.
Now I want to progress to something slightly more involved. I want to give my clients access to the products in a category.
I want my URI to be:
http:api/categories//products
E.g.
http:api/categories/books/products
and from there, I want to offer:
http:api/categories//products/
E.g.
http:api/categories/books/products/123
This gives my client the ability to transfer from one resource to another using a progressive series of links, which I see as a core principle of REST.
But I can't see a way of achieving this with Restler. I've seen some mention of JavaDoc comments being used to specify URI mapping, so I tried this:
class Products
{
/**
* url GET /categories/:catId/products/:prodId
*/
function get($catId=NULL, $prodId=NULL) {
// Get product($prodId) of category($catId)
}
}
But this doesn’t work. Restler doesn’t seem to take any information from the comment; it implicitly creates the URI route based on class name and function name.
Can anyone help? Am I missing something? Any advice would be much appreciated.
Everything is fine in the example above and what you are trying to achieve except one simple mistake that stopped it from working!
Your PHPDoc comment is missing #
Change your code as follows
<?php
class Products
{
/**
* #url GET /categories/:catId/products/:prodId
*/
function get($catId=NULL, $prodId=NULL) {
// Get product($prodId) of category($catId)
}
}
Also take a look at the related question
How do you organize Luracast Restler classes to create related route endpoints?