Repository files navigation

Gitter

Table of Contents

What is Rustless?

Build Status

Rustless is a REST-like API micro-framework for Rust. It's designed to provide a simple DSL to easily develop RESTful APIs on top of the Iron web framework. It has built-in support for common conventions, including multiple formats, subdomain/prefix restriction, content negotiation, versioning and much more.

Rustless in a port of Grape library from Ruby world. Based on hyper - an HTTP library for Rust.

Like Rust itself, Rustless is still in the early stages of development, so don't be surprised if APIs change and things break. If something's not working properly, file an issue or submit a pull request!

# Cargo.toml
[dependencies.rustless]
git = "https://github.com/rustless/rustless"

API docs

See also

Usage warning

Rustless is based on Iron, which is based on Hyper, which is synchronous. Hyper has a lot of limitations right now, and can't handle many simultaneous connections, especially with keep-alive. So it is highly recommended to use light asynchronous web server such as Nginx as a reverse proxy server with Rustless.

Basic Usage

Below is a simple example showing some of the more common features of Rustless.

#![feature(plugin)]#[plugin]externcrate rustless;externcrate hyper;externcrate iron;externcrate"rustc-serialize" as rustc_serialize;externcrate valico;use hyper::status::StatusCode;use iron::Iron;use rustless::{Application,Api,Nesting,Versioning};use rustc_serialize::json::ToJson;fnmain(){let api = Api::build(dsl!(|api| {// Specify API version
version("v1",Versioning::AcceptHeader("chat"));
prefix("api");// Create API for chats
mount(Api::build(dsl!(|chats_api| {
after(|client, _params| {
client.set_status(StatusCode::NotFound);Ok(())});// Add namespace
namespace("chats/:id", dsl!(|chat_ns| {// Valico settings for this namespace
params(|params| {
params.req_typed("id", valico::u64())});// Create endpoint for POST /chats/:id/users/:user_id
post("users/:user_id", dsl!(|endpoint| {// Add description
desc("Update user");// Valico settings for endpoint params
params(|params| {
params.req_typed("user_id", valico::u64());
params.req_typed("name", valico::string())});
handle(|client, params| {
client.json(&params.to_json())})}));}));})));}));let app = Application::new(api);Iron::new(app).listen("localhost:4000").unwrap();println!("On 4000");println!("Rustless server started!");}

Complex example

If you want to see how you can write come complex application using Rustless please see the example.

In that example please note these aspects:

  • Complex nested API with versioning.
  • CRUD operations with rust-postgres.
  • Swagger 2.0 intergration.
  • JSON Schema validations.
  • Error reporting.
  • Serializers.
  • File structure.
  • Integration with docopt.
  • Integration with deuterium-orm. Database migrations.

Mounting

In Rustless you can use three core entities to build your RESTful app: Api, Namespace and Endpoint.

  • Api can mount Api, Namespace and Endpoint
  • Namespace can mount Api, Namespace and Endpoint
Api::build(|api| {// Api inside Api example
api.mount(Api::build(dsl!(|nested_api| {// Endpoint definition
get("nested_info", dsl!|endpoint| {// endpoint.params(|params| {});// endpoint.desc("Some description");// Endpoint handler
handle(|client, _params| {
client.text("Some usefull info".to_string())})}));})))// The namespace method has a number of aliases, including: group,// resource, resources, and segment. Use whichever reads the best// for your API.
api.namespace("ns1", |ns1| {
ns1.group("ns2", |ns2| {
ns2.resource("ns3", |ns3| {
ns3.resources("ns4", |ns4| {
ns4.segment("ns5", |ns5| {// ...);})})})})})

Parameters validation and coercion

You can define validations and coercion options for your parameters using a DSL block inside Endpoint and Namespace definition. See Valico for more info about things you can do.

api.get("users/:user_id/messages/:message_id", |endpoint| {
endpoint.params(|params| {
params.req_typed("user_id",Valico::u64());
params.req_typed("message_id",Valico::u64());});// ...})

Use JSON Schema

Also you can use JSON Schema (IETF's draft v4) to validate your parameters. To use schemes in your application you need to make simple setup:

use valico::json_schema;use rustless::batteries::schemes;let scope = json_schema::Scope::new();// ... You can insert some external schemes here ...
schemes::enable_schemes(&mut app, scope).unwrap();

See Valico for more info about JSON Scheme usage inside DSL blocks.

Query strings

Rustless is intergated with queryst to allow smart query-string parsing end decoding (even with nesting, like foo[0][a]=a&foo[0][b]=b&foo[1][a]=aa&foo[1][b]=bb). See queryst for more info.

API versioning

There are three strategies in which clients can reach your API's endpoints:

  • Path
  • AcceptHeader
  • Param

Path versioning strategy

api.version("v1",Path);

Using this versioning strategy, clients should pass the desired version in the URL.

curl -H http://localhost:3000/v1/chats/

Header versioning strategy

api.version("v1",AcceptHeader("chat"));

Using this versioning strategy, clients should pass the desired version in the HTTP Accept head.

curl -H Accept:application/vnd.chat.v1+json http://localhost:3000/chats

Accept version format is the same as Github (uses)[https://developer.github.com/v3/media/].

Param versioning strategy

api.version("v1",Param("ver"));

Using this versioning strategy, clients should pass the desired version as a request parameter in the URL query.

curl -H http://localhost:9292/statuses/public_timeline?ver=v1

Respond with custom HTTP Status Code

By default Rustless returns a 200 status code for GET-Requests and 201 for POST-Requests. You can use status and set_status to query and set the actual HTTP Status Code

client.set_status(NotFound);

Use parameters

Request parameters are available through the params: JsonObject inside Endpoint handlers and all callbacks. This includes GET, POST and PUT parameters, along with any named parameters you specify in your route strings.

The request:

curl -d '{"text": "hello from echo"}' 'http://localhost:3000/echo' -H Content-Type:application/json -v

The Rustless endpoint:

api.post("", |endpoint| {
endpoint.handle(|client, params| {
client.json(params)})});

In the case of conflict between either of:

  • route string parameters
  • GET, POST and PUT parameters
  • the contents of the request body on POST and PUT

route string parameters will have precedence.

Redirecting

You can redirect to a new url temporarily (302) or permanently (301).

client.redirect("http://google.com");
client.redirect_permanent("http://google.com");

Errors firing

You can abort the execution of an API method by raising errors with error.

Define your error like this:

use rustless::errors::{Error,ErrorRefExt};#[deriving(Show)]pubstructUnauthorizedError;impl std::error::ErrorforUnauthorizedError{fndescription(&self) -> &str{return"UnauthorizedError";}}

And then throw:

client.error(UnauthorizedError);

Errors handling

By default Rustless wil respond all errors with status::InternalServerError.

Rustless can be told to rescue specific errors and return them in the custom API format.

api.error_formatter(|err, _media| {match err.downcast::<UnauthorizedError>(){Some(_) => {returnSome(Response::from_string(StatusCode::Unauthorized,"Please provide correct `token` parameter".to_string()))},None => None}});

Before and After callbacks

Blocks can be executed before or after every API call, using before, after, before_validation and after_validation.

Before and after callbacks execute in the following order:

  1. before
  2. before_validation
  3. validations
  4. after_validation
  5. the API call
  6. after

Steps 4, 5 and 6 only happen if validation succeeds.

The block applies to every API call within and below the current nesting level.

Secure API example

Api::build(dsl!(|api| {
prefix("api");
version("v1",Versioning::Path);
error_formatter(|err, _media| {match err.downcast::<UnauthorizedError>(){Some(_) => {returnSome(Response::from_string(StatusCode::Unauthorized,"Please provide correct `token` parameter".to_string()))},None => None}});
namespace("admin", dsl!(|admin_ns| {
params(|params| {
params.req_typed("token",Valico::string())});// Using after_validation callback to check token
after_validation(|&: _client, params| {match params.get("token"){// We can unwrap() safely because token in validated alreadySome(token) => if token.as_string().unwrap().as_slice() == "password1"{returnOk(())},None => ()}// Fire error from callback is token is wrongreturnErr(Box::new(UnauthorizedError)asBox<Error>)});// This `/api/admin/server_status` endpoint is secure now
get("server_status", dsl!(|endpoint| {
handle(|client, _params| {{let cookies = client.request.cookies();let signed_cookies = cookies.signed();let user_cookie = Cookie::new("session".to_string(),"verified".to_string());
signed_cookies.add(user_cookie);}
client.text("Everything is OK".to_string())})}));}))}))

JSON responses

Rustless includes JsonWay library to offer both complex JSON building DSL and configurable serializers for your objects. See API docs for details.

Also feel free to use any other serialization library you want.

Swagger 2.0

Rustless has a basic implementation of Swagger 2.0 specification. It is not fully complete and in future we need to implement:

  • JSON Schema support (when some appropriate JSON Schema library will appear);
  • Security parts of the specification;

But now you can already use Swagger 2.0:

letmut app = rustless::Application::new(rustless::Api::build(|api| {// ...
api.mount(swagger::create_api("api-docs"));// ...}))
swagger::enable(&mut app, swagger::Spec{info: swagger::Info{title:"Example API".to_string(),description:Some("Simple API to demonstration".to_string()),contact:Some(swagger::Contact{name:"Stanislav Panferov".to_string(),url:Some("http://panferov.me".to_string()),
..std::default::Default::default()}),license:Some(swagger::License{name:"MIT".to_string(),url:"http://opensource.org/licenses/MIT".to_string()}),
..std::default::Default::default()},host:"localhost:4000".to_string(),
..std::default::Default::default()});

After that you can use /api-docs path in Swagger UI to render your API structure.

Integration with PostgreSQL

We have an annotated example of such integration in postgres_example. Please try it and feel free to say your opinion.

Integration with Deuterium ORM

TODO: Example

About

REST-like API micro-framework for Rust. Works with Iron.

Resources

Contributing

Stars

0 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

Gitter

Table of Contents

What is Rustless?

Build Status

Rustless is a REST-like API micro-framework for Rust. It's designed to provide a simple DSL to easily develop RESTful APIs on top of the Iron web framework. It has built-in support for common conventions, including multiple formats, subdomain/prefix restriction, content negotiation, versioning and much more.

Rustless in a port of Grape library from Ruby world. Based on hyper - an HTTP library for Rust.

Like Rust itself, Rustless is still in the early stages of development, so don't be surprised if APIs change and things break. If something's not working properly, file an issue or submit a pull request!

# Cargo.toml
[dependencies.rustless]
git = "https://github.com/rustless/rustless"

API docs

See also

Usage warning

Rustless is based on Iron, which is based on Hyper, which is synchronous. Hyper has a lot of limitations right now, and can't handle many simultaneous connections, especially with keep-alive. So it is highly recommended to use light asynchronous web server such as Nginx as a reverse proxy server with Rustless.

Basic Usage

Below is a simple example showing some of the more common features of Rustless.

#![feature(plugin)]#[plugin]externcrate rustless;externcrate hyper;externcrate iron;externcrate"rustc-serialize" as rustc_serialize;externcrate valico;use hyper::status::StatusCode;use iron::Iron;use rustless::{Application,Api,Nesting,Versioning};use rustc_serialize::json::ToJson;fnmain(){let api = Api::build(dsl!(|api| {// Specify API version
version("v1",Versioning::AcceptHeader("chat"));
prefix("api");// Create API for chats
mount(Api::build(dsl!(|chats_api| {
after(|client, _params| {
client.set_status(StatusCode::NotFound);Ok(())});// Add namespace
namespace("chats/:id", dsl!(|chat_ns| {// Valico settings for this namespace
params(|params| {
params.req_typed("id", valico::u64())});// Create endpoint for POST /chats/:id/users/:user_id
post("users/:user_id", dsl!(|endpoint| {// Add description
desc("Update user");// Valico settings for endpoint params
params(|params| {
params.req_typed("user_id", valico::u64());
params.req_typed("name", valico::string())});
handle(|client, params| {
client.json(&params.to_json())})}));}));})));}));let app = Application::new(api);Iron::new(app).listen("localhost:4000").unwrap();println!("On 4000");println!("Rustless server started!");}

Complex example

If you want to see how you can write come complex application using Rustless please see the example.

In that example please note these aspects:

  • Complex nested API with versioning.
  • CRUD operations with rust-postgres.
  • Swagger 2.0 intergration.
  • JSON Schema validations.
  • Error reporting.
  • Serializers.
  • File structure.
  • Integration with docopt.
  • Integration with deuterium-orm. Database migrations.

Mounting

In Rustless you can use three core entities to build your RESTful app: Api, Namespace and Endpoint.

  • Api can mount Api, Namespace and Endpoint
  • Namespace can mount Api, Namespace and Endpoint
Api::build(|api| {// Api inside Api example
api.mount(Api::build(dsl!(|nested_api| {// Endpoint definition
get("nested_info", dsl!|endpoint| {// endpoint.params(|params| {});// endpoint.desc("Some description");// Endpoint handler
handle(|client, _params| {
client.text("Some usefull info".to_string())})}));})))// The namespace method has a number of aliases, including: group,// resource, resources, and segment. Use whichever reads the best// for your API.
api.namespace("ns1", |ns1| {
ns1.group("ns2", |ns2| {
ns2.resource("ns3", |ns3| {
ns3.resources("ns4", |ns4| {
ns4.segment("ns5", |ns5| {// ...);})})})})})

Parameters validation and coercion

You can define validations and coercion options for your parameters using a DSL block inside Endpoint and Namespace definition. See Valico for more info about things you can do.

api.get("users/:user_id/messages/:message_id", |endpoint| {
endpoint.params(|params| {
params.req_typed("user_id",Valico::u64());
params.req_typed("message_id",Valico::u64());});// ...})

Use JSON Schema

Also you can use JSON Schema (IETF's draft v4) to validate your parameters. To use schemes in your application you need to make simple setup:

use valico::json_schema;use rustless::batteries::schemes;let scope = json_schema::Scope::new();// ... You can insert some external schemes here ...
schemes::enable_schemes(&mut app, scope).unwrap();

See Valico for more info about JSON Scheme usage inside DSL blocks.

Query strings

Rustless is intergated with queryst to allow smart query-string parsing end decoding (even with nesting, like foo[0][a]=a&foo[0][b]=b&foo[1][a]=aa&foo[1][b]=bb). See queryst for more info.

API versioning

There are three strategies in which clients can reach your API's endpoints:

  • Path
  • AcceptHeader
  • Param

Path versioning strategy

api.version("v1",Path);

Using this versioning strategy, clients should pass the desired version in the URL.

curl -H http://localhost:3000/v1/chats/

Header versioning strategy

api.version("v1",AcceptHeader("chat"));

Using this versioning strategy, clients should pass the desired version in the HTTP Accept head.

curl -H Accept:application/vnd.chat.v1+json http://localhost:3000/chats

Accept version format is the same as Github (uses)[https://developer.github.com/v3/media/].

Param versioning strategy

api.version("v1",Param("ver"));

Using this versioning strategy, clients should pass the desired version as a request parameter in the URL query.

curl -H http://localhost:9292/statuses/public_timeline?ver=v1

Respond with custom HTTP Status Code

By default Rustless returns a 200 status code for GET-Requests and 201 for POST-Requests. You can use status and set_status to query and set the actual HTTP Status Code

client.set_status(NotFound);

Use parameters

Request parameters are available through the params: JsonObject inside Endpoint handlers and all callbacks. This includes GET, POST and PUT parameters, along with any named parameters you specify in your route strings.

The request:

curl -d '{"text": "hello from echo"}' 'http://localhost:3000/echo' -H Content-Type:application/json -v

The Rustless endpoint:

api.post("", |endpoint| {
endpoint.handle(|client, params| {
client.json(params)})});

In the case of conflict between either of:

  • route string parameters
  • GET, POST and PUT parameters
  • the contents of the request body on POST and PUT

route string parameters will have precedence.

Redirecting

You can redirect to a new url temporarily (302) or permanently (301).

client.redirect("http://google.com");
client.redirect_permanent("http://google.com");

Errors firing

You can abort the execution of an API method by raising errors with error.

Define your error like this:

use rustless::errors::{Error,ErrorRefExt};#[deriving(Show)]pubstructUnauthorizedError;impl std::error::ErrorforUnauthorizedError{fndescription(&self) -> &str{return"UnauthorizedError";}}

And then throw:

client.error(UnauthorizedError);

Errors handling

By default Rustless wil respond all errors with status::InternalServerError.

Rustless can be told to rescue specific errors and return them in the custom API format.

api.error_formatter(|err, _media| {match err.downcast::<UnauthorizedError>(){Some(_) => {returnSome(Response::from_string(StatusCode::Unauthorized,"Please provide correct `token` parameter".to_string()))},None => None}});

Before and After callbacks

Blocks can be executed before or after every API call, using before, after, before_validation and after_validation.

Before and after callbacks execute in the following order:

  1. before
  2. before_validation
  3. validations
  4. after_validation
  5. the API call
  6. after

Steps 4, 5 and 6 only happen if validation succeeds.

The block applies to every API call within and below the current nesting level.

Secure API example

Api::build(dsl!(|api| {
prefix("api");
version("v1",Versioning::Path);
error_formatter(|err, _media| {match err.downcast::<UnauthorizedError>(){Some(_) => {returnSome(Response::from_string(StatusCode::Unauthorized,"Please provide correct `token` parameter".to_string()))},None => None}});
namespace("admin", dsl!(|admin_ns| {
params(|params| {
params.req_typed("token",Valico::string())});// Using after_validation callback to check token
after_validation(|&: _client, params| {match params.get("token"){// We can unwrap() safely because token in validated alreadySome(token) => if token.as_string().unwrap().as_slice() == "password1"{returnOk(())},None => ()}// Fire error from callback is token is wrongreturnErr(Box::new(UnauthorizedError)asBox<Error>)});// This `/api/admin/server_status` endpoint is secure now
get("server_status", dsl!(|endpoint| {
handle(|client, _params| {{let cookies = client.request.cookies();let signed_cookies = cookies.signed();let user_cookie = Cookie::new("session".to_string(),"verified".to_string());
signed_cookies.add(user_cookie);}
client.text("Everything is OK".to_string())})}));}))}))

JSON responses

Rustless includes JsonWay library to offer both complex JSON building DSL and configurable serializers for your objects. See API docs for details.

Also feel free to use any other serialization library you want.

Swagger 2.0

Rustless has a basic implementation of Swagger 2.0 specification. It is not fully complete and in future we need to implement:

  • JSON Schema support (when some appropriate JSON Schema library will appear);
  • Security parts of the specification;

But now you can already use Swagger 2.0:

letmut app = rustless::Application::new(rustless::Api::build(|api| {// ...
api.mount(swagger::create_api("api-docs"));// ...}))
swagger::enable(&mut app, swagger::Spec{info: swagger::Info{title:"Example API".to_string(),description:Some("Simple API to demonstration".to_string()),contact:Some(swagger::Contact{name:"Stanislav Panferov".to_string(),url:Some("http://panferov.me".to_string()),
..std::default::Default::default()}),license:Some(swagger::License{name:"MIT".to_string(),url:"http://opensource.org/licenses/MIT".to_string()}),
..std::default::Default::default()},host:"localhost:4000".to_string(),
..std::default::Default::default()});

After that you can use /api-docs path in Swagger UI to render your API structure.

Integration with PostgreSQL

We have an annotated example of such integration in postgres_example. Please try it and feel free to say your opinion.

Integration with Deuterium ORM

TODO: Example

About

REST-like API micro-framework for Rust. Works with Iron.

Resources

Contributing

Stars

0 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Gitter

Table of Contents

What is Rustless?

Build Status

Rustless is a REST-like API micro-framework for Rust. It's designed to provide a simple DSL to easily develop RESTful APIs on top of the Iron web framework. It has built-in support for common conventions, including multiple formats, subdomain/prefix restriction, content negotiation, versioning and much more.

Rustless in a port of Grape library from Ruby world. Based on hyper - an HTTP library for Rust.

Like Rust itself, Rustless is still in the early stages of development, so don't be surprised if APIs change and things break. If something's not working properly, file an issue or submit a pull request!

# Cargo.toml
[dependencies.rustless]
git = "https://github.com/rustless/rustless"

API docs

See also

Usage warning

Rustless is based on Iron, which is based on Hyper, which is synchronous. Hyper has a lot of limitations right now, and can't handle many simultaneous connections, especially with keep-alive. So it is highly recommended to use light asynchronous web server such as Nginx as a reverse proxy server with Rustless.

Basic Usage

Below is a simple example showing some of the more common features of Rustless.

#![feature(plugin)]#[plugin]externcrate rustless;externcrate hyper;externcrate iron;externcrate"rustc-serialize" as rustc_serialize;externcrate valico;use hyper::status::StatusCode;use iron::Iron;use rustless::{Application,Api,Nesting,Versioning};use rustc_serialize::json::ToJson;fnmain(){let api = Api::build(dsl!(|api| {// Specify API version
version("v1",Versioning::AcceptHeader("chat"));
prefix("api");// Create API for chats
mount(Api::build(dsl!(|chats_api| {
after(|client, _params| {
client.set_status(StatusCode::NotFound);Ok(())});// Add namespace
namespace("chats/:id", dsl!(|chat_ns| {// Valico settings for this namespace
params(|params| {
params.req_typed("id", valico::u64())});// Create endpoint for POST /chats/:id/users/:user_id
post("users/:user_id", dsl!(|endpoint| {// Add description
desc("Update user");// Valico settings for endpoint params
params(|params| {
params.req_typed("user_id", valico::u64());
params.req_typed("name", valico::string())});
handle(|client, params| {
client.json(&params.to_json())})}));}));})));}));let app = Application::new(api);Iron::new(app).listen("localhost:4000").unwrap();println!("On 4000");println!("Rustless server started!");}

Complex example

If you want to see how you can write come complex application using Rustless please see the example.

In that example please note these aspects:

  • Complex nested API with versioning.
  • CRUD operations with rust-postgres.
  • Swagger 2.0 intergration.
  • JSON Schema validations.
  • Error reporting.
  • Serializers.
  • File structure.
  • Integration with docopt.
  • Integration with deuterium-orm. Database migrations.

Mounting

In Rustless you can use three core entities to build your RESTful app: Api, Namespace and Endpoint.

  • Api can mount Api, Namespace and Endpoint
  • Namespace can mount Api, Namespace and Endpoint
Api::build(|api| {// Api inside Api example
api.mount(Api::build(dsl!(|nested_api| {// Endpoint definition
get("nested_info", dsl!|endpoint| {// endpoint.params(|params| {});// endpoint.desc("Some description");// Endpoint handler
handle(|client, _params| {
client.text("Some usefull info".to_string())})}));})))// The namespace method has a number of aliases, including: group,// resource, resources, and segment. Use whichever reads the best// for your API.
api.namespace("ns1", |ns1| {
ns1.group("ns2", |ns2| {
ns2.resource("ns3", |ns3| {
ns3.resources("ns4", |ns4| {
ns4.segment("ns5", |ns5| {// ...);})})})})})

Parameters validation and coercion

You can define validations and coercion options for your parameters using a DSL block inside Endpoint and Namespace definition. See Valico for more info about things you can do.

api.get("users/:user_id/messages/:message_id", |endpoint| {
endpoint.params(|params| {
params.req_typed("user_id",Valico::u64());
params.req_typed("message_id",Valico::u64());});// ...})

Use JSON Schema

Also you can use JSON Schema (IETF's draft v4) to validate your parameters. To use schemes in your application you need to make simple setup:

use valico::json_schema;use rustless::batteries::schemes;let scope = json_schema::Scope::new();// ... You can insert some external schemes here ...
schemes::enable_schemes(&mut app, scope).unwrap();

See Valico for more info about JSON Scheme usage inside DSL blocks.

Query strings

Rustless is intergated with queryst to allow smart query-string parsing end decoding (even with nesting, like foo[0][a]=a&foo[0][b]=b&foo[1][a]=aa&foo[1][b]=bb). See queryst for more info.

API versioning

There are three strategies in which clients can reach your API's endpoints:

  • Path
  • AcceptHeader
  • Param

Path versioning strategy

api.version("v1",Path);

Using this versioning strategy, clients should pass the desired version in the URL.

curl -H http://localhost:3000/v1/chats/

Header versioning strategy

api.version("v1",AcceptHeader("chat"));

Using this versioning strategy, clients should pass the desired version in the HTTP Accept head.

curl -H Accept:application/vnd.chat.v1+json http://localhost:3000/chats

Accept version format is the same as Github (uses)[https://developer.github.com/v3/media/].

Param versioning strategy

api.version("v1",Param("ver"));

Using this versioning strategy, clients should pass the desired version as a request parameter in the URL query.

curl -H http://localhost:9292/statuses/public_timeline?ver=v1

Respond with custom HTTP Status Code

By default Rustless returns a 200 status code for GET-Requests and 201 for POST-Requests. You can use status and set_status to query and set the actual HTTP Status Code

client.set_status(NotFound);

Use parameters

Request parameters are available through the params: JsonObject inside Endpoint handlers and all callbacks. This includes GET, POST and PUT parameters, along with any named parameters you specify in your route strings.

The request:

curl -d '{"text": "hello from echo"}' 'http://localhost:3000/echo' -H Content-Type:application/json -v

The Rustless endpoint:

api.post("", |endpoint| {
endpoint.handle(|client, params| {
client.json(params)})});

In the case of conflict between either of:

  • route string parameters
  • GET, POST and PUT parameters
  • the contents of the request body on POST and PUT

route string parameters will have precedence.

Redirecting

You can redirect to a new url temporarily (302) or permanently (301).

client.redirect("http://google.com");
client.redirect_permanent("http://google.com");

Errors firing

You can abort the execution of an API method by raising errors with error.

Define your error like this:

use rustless::errors::{Error,ErrorRefExt};#[deriving(Show)]pubstructUnauthorizedError;impl std::error::ErrorforUnauthorizedError{fndescription(&self) -> &str{return"UnauthorizedError";}}

And then throw:

client.error(UnauthorizedError);

Errors handling

By default Rustless wil respond all errors with status::InternalServerError.

Rustless can be told to rescue specific errors and return them in the custom API format.

api.error_formatter(|err, _media| {match err.downcast::<UnauthorizedError>(){Some(_) => {returnSome(Response::from_string(StatusCode::Unauthorized,"Please provide correct `token` parameter".to_string()))},None => None}});

Before and After callbacks

Blocks can be executed before or after every API call, using before, after, before_validation and after_validation.

Before and after callbacks execute in the following order:

  1. before
  2. before_validation
  3. validations
  4. after_validation
  5. the API call
  6. after

Steps 4, 5 and 6 only happen if validation succeeds.

The block applies to every API call within and below the current nesting level.

Secure API example

Api::build(dsl!(|api| {
prefix("api");
version("v1",Versioning::Path);
error_formatter(|err, _media| {match err.downcast::<UnauthorizedError>(){Some(_) => {returnSome(Response::from_string(StatusCode::Unauthorized,"Please provide correct `token` parameter".to_string()))},None => None}});
namespace("admin", dsl!(|admin_ns| {
params(|params| {
params.req_typed("token",Valico::string())});// Using after_validation callback to check token
after_validation(|&: _client, params| {match params.get("token"){// We can unwrap() safely because token in validated alreadySome(token) => if token.as_string().unwrap().as_slice() == "password1"{returnOk(())},None => ()}// Fire error from callback is token is wrongreturnErr(Box::new(UnauthorizedError)asBox<Error>)});// This `/api/admin/server_status` endpoint is secure now
get("server_status", dsl!(|endpoint| {
handle(|client, _params| {{let cookies = client.request.cookies();let signed_cookies = cookies.signed();let user_cookie = Cookie::new("session".to_string(),"verified".to_string());
signed_cookies.add(user_cookie);}
client.text("Everything is OK".to_string())})}));}))}))

JSON responses

Rustless includes JsonWay library to offer both complex JSON building DSL and configurable serializers for your objects. See API docs for details.

Also feel free to use any other serialization library you want.

Swagger 2.0

Rustless has a basic implementation of Swagger 2.0 specification. It is not fully complete and in future we need to implement:

  • JSON Schema support (when some appropriate JSON Schema library will appear);
  • Security parts of the specification;

But now you can already use Swagger 2.0:

letmut app = rustless::Application::new(rustless::Api::build(|api| {// ...
api.mount(swagger::create_api("api-docs"));// ...}))
swagger::enable(&mut app, swagger::Spec{info: swagger::Info{title:"Example API".to_string(),description:Some("Simple API to demonstration".to_string()),contact:Some(swagger::Contact{name:"Stanislav Panferov".to_string(),url:Some("http://panferov.me".to_string()),
..std::default::Default::default()}),license:Some(swagger::License{name:"MIT".to_string(),url:"http://opensource.org/licenses/MIT".to_string()}),
..std::default::Default::default()},host:"localhost:4000".to_string(),
..std::default::Default::default()});

After that you can use /api-docs path in Swagger UI to render your API structure.

Integration with PostgreSQL

We have an annotated example of such integration in postgres_example. Please try it and feel free to say your opinion.

Integration with Deuterium ORM

TODO: Example

About

REST-like API micro-framework for Rust. Works with Iron.

Resources

Contributing

Stars

0 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Gitter

Table of Contents

What is Rustless?

Build Status

Rustless is a REST-like API micro-framework for Rust. It's designed to provide a simple DSL to easily develop RESTful APIs on top of the Iron web framework. It has built-in support for common conventions, including multiple formats, subdomain/prefix restriction, content negotiation, versioning and much more.

Rustless in a port of Grape library from Ruby world. Based on hyper - an HTTP library for Rust.

Like Rust itself, Rustless is still in the early stages of development, so don't be surprised if APIs change and things break. If something's not working properly, file an issue or submit a pull request!

# Cargo.toml
[dependencies.rustless]
git = "https://github.com/rustless/rustless"

API docs

See also

Usage warning

Rustless is based on Iron, which is based on Hyper, which is synchronous. Hyper has a lot of limitations right now, and can't handle many simultaneous connections, especially with keep-alive. So it is highly recommended to use light asynchronous web server such as Nginx as a reverse proxy server with Rustless.

Basic Usage

Below is a simple example showing some of the more common features of Rustless.

#![feature(plugin)]#[plugin]externcrate rustless;externcrate hyper;externcrate iron;externcrate"rustc-serialize" as rustc_serialize;externcrate valico;use hyper::status::StatusCode;use iron::Iron;use rustless::{Application,Api,Nesting,Versioning};use rustc_serialize::json::ToJson;fnmain(){let api = Api::build(dsl!(|api| {// Specify API version
version("v1",Versioning::AcceptHeader("chat"));
prefix("api");// Create API for chats
mount(Api::build(dsl!(|chats_api| {
after(|client, _params| {
client.set_status(StatusCode::NotFound);Ok(())});// Add namespace
namespace("chats/:id", dsl!(|chat_ns| {// Valico settings for this namespace
params(|params| {
params.req_typed("id", valico::u64())});// Create endpoint for POST /chats/:id/users/:user_id
post("users/:user_id", dsl!(|endpoint| {// Add description
desc("Update user");// Valico settings for endpoint params
params(|params| {
params.req_typed("user_id", valico::u64());
params.req_typed("name", valico::string())});
handle(|client, params| {
client.json(&params.to_json())})}));}));})));}));let app = Application::new(api);Iron::new(app).listen("localhost:4000").unwrap();println!("On 4000");println!("Rustless server started!");}

Complex example

If you want to see how you can write come complex application using Rustless please see the example.

In that example please note these aspects:

  • Complex nested API with versioning.
  • CRUD operations with rust-postgres.
  • Swagger 2.0 intergration.
  • JSON Schema validations.
  • Error reporting.
  • Serializers.
  • File structure.
  • Integration with docopt.
  • Integration with deuterium-orm. Database migrations.

Mounting

In Rustless you can use three core entities to build your RESTful app: Api, Namespace and Endpoint.

  • Api can mount Api, Namespace and Endpoint
  • Namespace can mount Api, Namespace and Endpoint
Api::build(|api| {// Api inside Api example
api.mount(Api::build(dsl!(|nested_api| {// Endpoint definition
get("nested_info", dsl!|endpoint| {// endpoint.params(|params| {});// endpoint.desc("Some description");// Endpoint handler
handle(|client, _params| {
client.text("Some usefull info".to_string())})}));})))// The namespace method has a number of aliases, including: group,// resource, resources, and segment. Use whichever reads the best// for your API.
api.namespace("ns1", |ns1| {
ns1.group("ns2", |ns2| {
ns2.resource("ns3", |ns3| {
ns3.resources("ns4", |ns4| {
ns4.segment("ns5", |ns5| {// ...);})})})})})

Parameters validation and coercion

You can define validations and coercion options for your parameters using a DSL block inside Endpoint and Namespace definition. See Valico for more info about things you can do.

api.get("users/:user_id/messages/:message_id", |endpoint| {
endpoint.params(|params| {
params.req_typed("user_id",Valico::u64());
params.req_typed("message_id",Valico::u64());});// ...})

Use JSON Schema

Also you can use JSON Schema (IETF's draft v4) to validate your parameters. To use schemes in your application you need to make simple setup:

use valico::json_schema;use rustless::batteries::schemes;let scope = json_schema::Scope::new();// ... You can insert some external schemes here ...
schemes::enable_schemes(&mut app, scope).unwrap();

See Valico for more info about JSON Scheme usage inside DSL blocks.

Query strings

Rustless is intergated with queryst to allow smart query-string parsing end decoding (even with nesting, like foo[0][a]=a&foo[0][b]=b&foo[1][a]=aa&foo[1][b]=bb). See queryst for more info.

API versioning

There are three strategies in which clients can reach your API's endpoints:

  • Path
  • AcceptHeader
  • Param

Path versioning strategy

api.version("v1",Path);

Using this versioning strategy, clients should pass the desired version in the URL.

curl -H http://localhost:3000/v1/chats/

Header versioning strategy

api.version("v1",AcceptHeader("chat"));

Using this versioning strategy, clients should pass the desired version in the HTTP Accept head.

curl -H Accept:application/vnd.chat.v1+json http://localhost:3000/chats

Accept version format is the same as Github (uses)[https://developer.github.com/v3/media/].

Param versioning strategy

api.version("v1",Param("ver"));

Using this versioning strategy, clients should pass the desired version as a request parameter in the URL query.

curl -H http://localhost:9292/statuses/public_timeline?ver=v1

Respond with custom HTTP Status Code

By default Rustless returns a 200 status code for GET-Requests and 201 for POST-Requests. You can use status and set_status to query and set the actual HTTP Status Code

client.set_status(NotFound);

Use parameters

Request parameters are available through the params: JsonObject inside Endpoint handlers and all callbacks. This includes GET, POST and PUT parameters, along with any named parameters you specify in your route strings.

The request:

curl -d '{"text": "hello from echo"}' 'http://localhost:3000/echo' -H Content-Type:application/json -v

The Rustless endpoint:

api.post("", |endpoint| {
endpoint.handle(|client, params| {
client.json(params)})});

In the case of conflict between either of:

  • route string parameters
  • GET, POST and PUT parameters
  • the contents of the request body on POST and PUT

route string parameters will have precedence.

Redirecting

You can redirect to a new url temporarily (302) or permanently (301).

client.redirect("http://google.com");
client.redirect_permanent("http://google.com");

Errors firing

You can abort the execution of an API method by raising errors with error.

Define your error like this:

use rustless::errors::{Error,ErrorRefExt};#[deriving(Show)]pubstructUnauthorizedError;impl std::error::ErrorforUnauthorizedError{fndescription(&self) -> &str{return"UnauthorizedError";}}

And then throw:

client.error(UnauthorizedError);

Errors handling

By default Rustless wil respond all errors with status::InternalServerError.

Rustless can be told to rescue specific errors and return them in the custom API format.

api.error_formatter(|err, _media| {match err.downcast::<UnauthorizedError>(){Some(_) => {returnSome(Response::from_string(StatusCode::Unauthorized,"Please provide correct `token` parameter".to_string()))},None => None}});

Before and After callbacks

Blocks can be executed before or after every API call, using before, after, before_validation and after_validation.

Before and after callbacks execute in the following order:

  1. before
  2. before_validation
  3. validations
  4. after_validation
  5. the API call
  6. after

Steps 4, 5 and 6 only happen if validation succeeds.

The block applies to every API call within and below the current nesting level.

Secure API example

Api::build(dsl!(|api| {
prefix("api");
version("v1",Versioning::Path);
error_formatter(|err, _media| {match err.downcast::<UnauthorizedError>(){Some(_) => {returnSome(Response::from_string(StatusCode::Unauthorized,"Please provide correct `token` parameter".to_string()))},None => None}});
namespace("admin", dsl!(|admin_ns| {
params(|params| {
params.req_typed("token",Valico::string())});// Using after_validation callback to check token
after_validation(|&: _client, params| {match params.get("token"){// We can unwrap() safely because token in validated alreadySome(token) => if token.as_string().unwrap().as_slice() == "password1"{returnOk(())},None => ()}// Fire error from callback is token is wrongreturnErr(Box::new(UnauthorizedError)asBox<Error>)});// This `/api/admin/server_status` endpoint is secure now
get("server_status", dsl!(|endpoint| {
handle(|client, _params| {{let cookies = client.request.cookies();let signed_cookies = cookies.signed();let user_cookie = Cookie::new("session".to_string(),"verified".to_string());
signed_cookies.add(user_cookie);}
client.text("Everything is OK".to_string())})}));}))}))

JSON responses

Rustless includes JsonWay library to offer both complex JSON building DSL and configurable serializers for your objects. See API docs for details.

Also feel free to use any other serialization library you want.

Swagger 2.0

Rustless has a basic implementation of Swagger 2.0 specification. It is not fully complete and in future we need to implement:

  • JSON Schema support (when some appropriate JSON Schema library will appear);
  • Security parts of the specification;

But now you can already use Swagger 2.0:

letmut app = rustless::Application::new(rustless::Api::build(|api| {// ...
api.mount(swagger::create_api("api-docs"));// ...}))
swagger::enable(&mut app, swagger::Spec{info: swagger::Info{title:"Example API".to_string(),description:Some("Simple API to demonstration".to_string()),contact:Some(swagger::Contact{name:"Stanislav Panferov".to_string(),url:Some("http://panferov.me".to_string()),
..std::default::Default::default()}),license:Some(swagger::License{name:"MIT".to_string(),url:"http://opensource.org/licenses/MIT".to_string()}),
..std::default::Default::default()},host:"localhost:4000".to_string(),
..std::default::Default::default()});

After that you can use /api-docs path in Swagger UI to render your API structure.

Integration with PostgreSQL

We have an annotated example of such integration in postgres_example. Please try it and feel free to say your opinion.

Integration with Deuterium ORM

TODO: Example

About

REST-like API micro-framework for Rust. Works with Iron.

Resources

Contributing

Stars

0 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

Gitter

Table of Contents

What is Rustless?

Build Status

Rustless is a REST-like API micro-framework for Rust. It's designed to provide a simple DSL to easily develop RESTful APIs on top of the Iron web framework. It has built-in support for common conventions, including multiple formats, subdomain/prefix restriction, content negotiation, versioning and much more.

Rustless in a port of Grape library from Ruby world. Based on hyper - an HTTP library for Rust.

Like Rust itself, Rustless is still in the early stages of development, so don't be surprised if APIs change and things break. If something's not working properly, file an issue or submit a pull request!

# Cargo.toml
[dependencies.rustless]
git = "https://github.com/rustless/rustless"

API docs

See also

Usage warning

Rustless is based on Iron, which is based on Hyper, which is synchronous. Hyper has a lot of limitations right now, and can't handle many simultaneous connections, especially with keep-alive. So it is highly recommended to use light asynchronous web server such as Nginx as a reverse proxy server with Rustless.

Basic Usage

Below is a simple example showing some of the more common features of Rustless.

#![feature(plugin)]#[plugin]externcrate rustless;externcrate hyper;externcrate iron;externcrate"rustc-serialize" as rustc_serialize;externcrate valico;use hyper::status::StatusCode;use iron::Iron;use rustless::{Application,Api,Nesting,Versioning};use rustc_serialize::json::ToJson;fnmain(){let api = Api::build(dsl!(|api| {// Specify API version
version("v1",Versioning::AcceptHeader("chat"));
prefix("api");// Create API for chats
mount(Api::build(dsl!(|chats_api| {
after(|client, _params| {
client.set_status(StatusCode::NotFound);Ok(())});// Add namespace
namespace("chats/:id", dsl!(|chat_ns| {// Valico settings for this namespace
params(|params| {
params.req_typed("id", valico::u64())});// Create endpoint for POST /chats/:id/users/:user_id
post("users/:user_id", dsl!(|endpoint| {// Add description
desc("Update user");// Valico settings for endpoint params
params(|params| {
params.req_typed("user_id", valico::u64());
params.req_typed("name", valico::string())});
handle(|client, params| {
client.json(&params.to_json())})}));}));})));}));let app = Application::new(api);Iron::new(app).listen("localhost:4000").unwrap();println!("On 4000");println!("Rustless server started!");}

Complex example

If you want to see how you can write come complex application using Rustless please see the example.

In that example please note these aspects:

  • Complex nested API with versioning.
  • CRUD operations with rust-postgres.
  • Swagger 2.0 intergration.
  • JSON Schema validations.
  • Error reporting.
  • Serializers.
  • File structure.
  • Integration with docopt.
  • Integration with deuterium-orm. Database migrations.

Mounting

In Rustless you can use three core entities to build your RESTful app: Api, Namespace and Endpoint.

  • Api can mount Api, Namespace and Endpoint
  • Namespace can mount Api, Namespace and Endpoint
Api::build(|api| {// Api inside Api example
api.mount(Api::build(dsl!(|nested_api| {// Endpoint definition
get("nested_info", dsl!|endpoint| {// endpoint.params(|params| {});// endpoint.desc("Some description");// Endpoint handler
handle(|client, _params| {
client.text("Some usefull info".to_string())})}));})))// The namespace method has a number of aliases, including: group,// resource, resources, and segment. Use whichever reads the best// for your API.
api.namespace("ns1", |ns1| {
ns1.group("ns2", |ns2| {
ns2.resource("ns3", |ns3| {
ns3.resources("ns4", |ns4| {
ns4.segment("ns5", |ns5| {// ...);})})})})})

Parameters validation and coercion

You can define validations and coercion options for your parameters using a DSL block inside Endpoint and Namespace definition. See Valico for more info about things you can do.

api.get("users/:user_id/messages/:message_id", |endpoint| {
endpoint.params(|params| {
params.req_typed("user_id",Valico::u64());
params.req_typed("message_id",Valico::u64());});// ...})

Use JSON Schema

Also you can use JSON Schema (IETF's draft v4) to validate your parameters. To use schemes in your application you need to make simple setup:

use valico::json_schema;use rustless::batteries::schemes;let scope = json_schema::Scope::new();// ... You can insert some external schemes here ...
schemes::enable_schemes(&mut app, scope).unwrap();

See Valico for more info about JSON Scheme usage inside DSL blocks.

Query strings

Rustless is intergated with queryst to allow smart query-string parsing end decoding (even with nesting, like foo[0][a]=a&foo[0][b]=b&foo[1][a]=aa&foo[1][b]=bb). See queryst for more info.

API versioning

There are three strategies in which clients can reach your API's endpoints:

  • Path
  • AcceptHeader
  • Param

Path versioning strategy

api.version("v1",Path);

Using this versioning strategy, clients should pass the desired version in the URL.

curl -H http://localhost:3000/v1/chats/

Header versioning strategy

api.version("v1",AcceptHeader("chat"));

Using this versioning strategy, clients should pass the desired version in the HTTP Accept head.

curl -H Accept:application/vnd.chat.v1+json http://localhost:3000/chats

Accept version format is the same as Github (uses)[https://developer.github.com/v3/media/].

Param versioning strategy

api.version("v1",Param("ver"));

Using this versioning strategy, clients should pass the desired version as a request parameter in the URL query.

curl -H http://localhost:9292/statuses/public_timeline?ver=v1

Respond with custom HTTP Status Code

By default Rustless returns a 200 status code for GET-Requests and 201 for POST-Requests. You can use status and set_status to query and set the actual HTTP Status Code

client.set_status(NotFound);

Use parameters

Request parameters are available through the params: JsonObject inside Endpoint handlers and all callbacks. This includes GET, POST and PUT parameters, along with any named parameters you specify in your route strings.

The request:

curl -d '{"text": "hello from echo"}' 'http://localhost:3000/echo' -H Content-Type:application/json -v

The Rustless endpoint:

api.post("", |endpoint| {
endpoint.handle(|client, params| {
client.json(params)})});

In the case of conflict between either of:

  • route string parameters
  • GET, POST and PUT parameters
  • the contents of the request body on POST and PUT

route string parameters will have precedence.

Redirecting

You can redirect to a new url temporarily (302) or permanently (301).

client.redirect("http://google.com");
client.redirect_permanent("http://google.com");

Errors firing

You can abort the execution of an API method by raising errors with error.

Define your error like this:

use rustless::errors::{Error,ErrorRefExt};#[deriving(Show)]pubstructUnauthorizedError;impl std::error::ErrorforUnauthorizedError{fndescription(&self) -> &str{return"UnauthorizedError";}}

And then throw:

client.error(UnauthorizedError);

Errors handling

By default Rustless wil respond all errors with status::InternalServerError.

Rustless can be told to rescue specific errors and return them in the custom API format.

api.error_formatter(|err, _media| {match err.downcast::<UnauthorizedError>(){Some(_) => {returnSome(Response::from_string(StatusCode::Unauthorized,"Please provide correct `token` parameter".to_string()))},None => None}});

Before and After callbacks

Blocks can be executed before or after every API call, using before, after, before_validation and after_validation.

Before and after callbacks execute in the following order:

  1. before
  2. before_validation
  3. validations
  4. after_validation
  5. the API call
  6. after

Steps 4, 5 and 6 only happen if validation succeeds.

The block applies to every API call within and below the current nesting level.

Secure API example

Api::build(dsl!(|api| {
prefix("api");
version("v1",Versioning::Path);
error_formatter(|err, _media| {match err.downcast::<UnauthorizedError>(){Some(_) => {returnSome(Response::from_string(StatusCode::Unauthorized,"Please provide correct `token` parameter".to_string()))},None => None}});
namespace("admin", dsl!(|admin_ns| {
params(|params| {
params.req_typed("token",Valico::string())});// Using after_validation callback to check token
after_validation(|&: _client, params| {match params.get("token"){// We can unwrap() safely because token in validated alreadySome(token) => if token.as_string().unwrap().as_slice() == "password1"{returnOk(())},None => ()}// Fire error from callback is token is wrongreturnErr(Box::new(UnauthorizedError)asBox<Error>)});// This `/api/admin/server_status` endpoint is secure now
get("server_status", dsl!(|endpoint| {
handle(|client, _params| {{let cookies = client.request.cookies();let signed_cookies = cookies.signed();let user_cookie = Cookie::new("session".to_string(),"verified".to_string());
signed_cookies.add(user_cookie);}
client.text("Everything is OK".to_string())})}));}))}))

JSON responses

Rustless includes JsonWay library to offer both complex JSON building DSL and configurable serializers for your objects. See API docs for details.

Also feel free to use any other serialization library you want.

Swagger 2.0

Rustless has a basic implementation of Swagger 2.0 specification. It is not fully complete and in future we need to implement:

  • JSON Schema support (when some appropriate JSON Schema library will appear);
  • Security parts of the specification;

But now you can already use Swagger 2.0:

letmut app = rustless::Application::new(rustless::Api::build(|api| {// ...
api.mount(swagger::create_api("api-docs"));// ...}))
swagger::enable(&mut app, swagger::Spec{info: swagger::Info{title:"Example API".to_string(),description:Some("Simple API to demonstration".to_string()),contact:Some(swagger::Contact{name:"Stanislav Panferov".to_string(),url:Some("http://panferov.me".to_string()),
..std::default::Default::default()}),license:Some(swagger::License{name:"MIT".to_string(),url:"http://opensource.org/licenses/MIT".to_string()}),
..std::default::Default::default()},host:"localhost:4000".to_string(),
..std::default::Default::default()});

After that you can use /api-docs path in Swagger UI to render your API structure.

Integration with PostgreSQL

We have an annotated example of such integration in postgres_example. Please try it and feel free to say your opinion.

Integration with Deuterium ORM

TODO: Example

About

REST-like API micro-framework for Rust. Works with Iron.

Resources

Contributing

Stars

0 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Gitter

Table of Contents

What is Rustless?

Build Status

Rustless is a REST-like API micro-framework for Rust. It's designed to provide a simple DSL to easily develop RESTful APIs on top of the Iron web framework. It has built-in support for common conventions, including multiple formats, subdomain/prefix restriction, content negotiation, versioning and much more.

Rustless in a port of Grape library from Ruby world. Based on hyper - an HTTP library for Rust.

Like Rust itself, Rustless is still in the early stages of development, so don't be surprised if APIs change and things break. If something's not working properly, file an issue or submit a pull request!

# Cargo.toml
[dependencies.rustless]
git = "https://github.com/rustless/rustless"

API docs

See also

Usage warning

Rustless is based on Iron, which is based on Hyper, which is synchronous. Hyper has a lot of limitations right now, and can't handle many simultaneous connections, especially with keep-alive. So it is highly recommended to use light asynchronous web server such as Nginx as a reverse proxy server with Rustless.

Basic Usage

Below is a simple example showing some of the more common features of Rustless.

#![feature(plugin)]#[plugin]externcrate rustless;externcrate hyper;externcrate iron;externcrate"rustc-serialize" as rustc_serialize;externcrate valico;use hyper::status::StatusCode;use iron::Iron;use rustless::{Application,Api,Nesting,Versioning};use rustc_serialize::json::ToJson;fnmain(){let api = Api::build(dsl!(|api| {// Specify API version
version("v1",Versioning::AcceptHeader("chat"));
prefix("api");// Create API for chats
mount(Api::build(dsl!(|chats_api| {
after(|client, _params| {
client.set_status(StatusCode::NotFound);Ok(())});// Add namespace
namespace("chats/:id", dsl!(|chat_ns| {// Valico settings for this namespace
params(|params| {
params.req_typed("id", valico::u64())});// Create endpoint for POST /chats/:id/users/:user_id
post("users/:user_id", dsl!(|endpoint| {// Add description
desc("Update user");// Valico settings for endpoint params
params(|params| {
params.req_typed("user_id", valico::u64());
params.req_typed("name", valico::string())});
handle(|client, params| {
client.json(&params.to_json())})}));}));})));}));let app = Application::new(api);Iron::new(app).listen("localhost:4000").unwrap();println!("On 4000");println!("Rustless server started!");}

Complex example

If you want to see how you can write come complex application using Rustless please see the example.

In that example please note these aspects:

  • Complex nested API with versioning.
  • CRUD operations with rust-postgres.
  • Swagger 2.0 intergration.
  • JSON Schema validations.
  • Error reporting.
  • Serializers.
  • File structure.
  • Integration with docopt.
  • Integration with deuterium-orm. Database migrations.

Mounting

In Rustless you can use three core entities to build your RESTful app: Api, Namespace and Endpoint.

  • Api can mount Api, Namespace and Endpoint
  • Namespace can mount Api, Namespace and Endpoint
Api::build(|api| {// Api inside Api example
api.mount(Api::build(dsl!(|nested_api| {// Endpoint definition
get("nested_info", dsl!|endpoint| {// endpoint.params(|params| {});// endpoint.desc("Some description");// Endpoint handler
handle(|client, _params| {
client.text("Some usefull info".to_string())})}));})))// The namespace method has a number of aliases, including: group,// resource, resources, and segment. Use whichever reads the best// for your API.
api.namespace("ns1", |ns1| {
ns1.group("ns2", |ns2| {
ns2.resource("ns3", |ns3| {
ns3.resources("ns4", |ns4| {
ns4.segment("ns5", |ns5| {// ...);})})})})})

Parameters validation and coercion

You can define validations and coercion options for your parameters using a DSL block inside Endpoint and Namespace definition. See Valico for more info about things you can do.

api.get("users/:user_id/messages/:message_id", |endpoint| {
endpoint.params(|params| {
params.req_typed("user_id",Valico::u64());
params.req_typed("message_id",Valico::u64());});// ...})

Use JSON Schema

Also you can use JSON Schema (IETF's draft v4) to validate your parameters. To use schemes in your application you need to make simple setup:

use valico::json_schema;use rustless::batteries::schemes;let scope = json_schema::Scope::new();// ... You can insert some external schemes here ...
schemes::enable_schemes(&mut app, scope).unwrap();

See Valico for more info about JSON Scheme usage inside DSL blocks.

Query strings

Rustless is intergated with queryst to allow smart query-string parsing end decoding (even with nesting, like foo[0][a]=a&foo[0][b]=b&foo[1][a]=aa&foo[1][b]=bb). See queryst for more info.

API versioning

There are three strategies in which clients can reach your API's endpoints:

  • Path
  • AcceptHeader
  • Param

Path versioning strategy

api.version("v1",Path);

Using this versioning strategy, clients should pass the desired version in the URL.

curl -H http://localhost:3000/v1/chats/

Header versioning strategy

api.version("v1",AcceptHeader("chat"));

Using this versioning strategy, clients should pass the desired version in the HTTP Accept head.

curl -H Accept:application/vnd.chat.v1+json http://localhost:3000/chats

Accept version format is the same as Github (uses)[https://developer.github.com/v3/media/].

Param versioning strategy

api.version("v1",Param("ver"));

Using this versioning strategy, clients should pass the desired version as a request parameter in the URL query.

curl -H http://localhost:9292/statuses/public_timeline?ver=v1

Respond with custom HTTP Status Code

By default Rustless returns a 200 status code for GET-Requests and 201 for POST-Requests. You can use status and set_status to query and set the actual HTTP Status Code

client.set_status(NotFound);

Use parameters

Request parameters are available through the params: JsonObject inside Endpoint handlers and all callbacks. This includes GET, POST and PUT parameters, along with any named parameters you specify in your route strings.

The request:

curl -d '{"text": "hello from echo"}' 'http://localhost:3000/echo' -H Content-Type:application/json -v

The Rustless endpoint:

api.post("", |endpoint| {
endpoint.handle(|client, params| {
client.json(params)})});

In the case of conflict between either of:

  • route string parameters
  • GET, POST and PUT parameters
  • the contents of the request body on POST and PUT

route string parameters will have precedence.

Redirecting

You can redirect to a new url temporarily (302) or permanently (301).

client.redirect("http://google.com");
client.redirect_permanent("http://google.com");

Errors firing

You can abort the execution of an API method by raising errors with error.

Define your error like this:

use rustless::errors::{Error,ErrorRefExt};#[deriving(Show)]pubstructUnauthorizedError;impl std::error::ErrorforUnauthorizedError{fndescription(&self) -> &str{return"UnauthorizedError";}}

And then throw:

client.error(UnauthorizedError);

Errors handling

By default Rustless wil respond all errors with status::InternalServerError.

Rustless can be told to rescue specific errors and return them in the custom API format.

api.error_formatter(|err, _media| {match err.downcast::<UnauthorizedError>(){Some(_) => {returnSome(Response::from_string(StatusCode::Unauthorized,"Please provide correct `token` parameter".to_string()))},None => None}});

Before and After callbacks

Blocks can be executed before or after every API call, using before, after, before_validation and after_validation.

Before and after callbacks execute in the following order:

  1. before
  2. before_validation
  3. validations
  4. after_validation
  5. the API call
  6. after

Steps 4, 5 and 6 only happen if validation succeeds.

The block applies to every API call within and below the current nesting level.

Secure API example

Api::build(dsl!(|api| {
prefix("api");
version("v1",Versioning::Path);
error_formatter(|err, _media| {match err.downcast::<UnauthorizedError>(){Some(_) => {returnSome(Response::from_string(StatusCode::Unauthorized,"Please provide correct `token` parameter".to_string()))},None => None}});
namespace("admin", dsl!(|admin_ns| {
params(|params| {
params.req_typed("token",Valico::string())});// Using after_validation callback to check token
after_validation(|&: _client, params| {match params.get("token"){// We can unwrap() safely because token in validated alreadySome(token) => if token.as_string().unwrap().as_slice() == "password1"{returnOk(())},None => ()}// Fire error from callback is token is wrongreturnErr(Box::new(UnauthorizedError)asBox<Error>)});// This `/api/admin/server_status` endpoint is secure now
get("server_status", dsl!(|endpoint| {
handle(|client, _params| {{let cookies = client.request.cookies();let signed_cookies = cookies.signed();let user_cookie = Cookie::new("session".to_string(),"verified".to_string());
signed_cookies.add(user_cookie);}
client.text("Everything is OK".to_string())})}));}))}))

JSON responses

Rustless includes JsonWay library to offer both complex JSON building DSL and configurable serializers for your objects. See API docs for details.

Also feel free to use any other serialization library you want.

Swagger 2.0

Rustless has a basic implementation of Swagger 2.0 specification. It is not fully complete and in future we need to implement:

  • JSON Schema support (when some appropriate JSON Schema library will appear);
  • Security parts of the specification;

But now you can already use Swagger 2.0:

letmut app = rustless::Application::new(rustless::Api::build(|api| {// ...
api.mount(swagger::create_api("api-docs"));// ...}))
swagger::enable(&mut app, swagger::Spec{info: swagger::Info{title:"Example API".to_string(),description:Some("Simple API to demonstration".to_string()),contact:Some(swagger::Contact{name:"Stanislav Panferov".to_string(),url:Some("http://panferov.me".to_string()),
..std::default::Default::default()}),license:Some(swagger::License{name:"MIT".to_string(),url:"http://opensource.org/licenses/MIT".to_string()}),
..std::default::Default::default()},host:"localhost:4000".to_string(),
..std::default::Default::default()});

After that you can use /api-docs path in Swagger UI to render your API structure.

Integration with PostgreSQL

We have an annotated example of such integration in postgres_example. Please try it and feel free to say your opinion.

Integration with Deuterium ORM

TODO: Example

About

REST-like API micro-framework for Rust. Works with Iron.

Resources

Contributing

Stars

0 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Gitter

Table of Contents

What is Rustless?

Build Status

Rustless is a REST-like API micro-framework for Rust. It's designed to provide a simple DSL to easily develop RESTful APIs on top of the Iron web framework. It has built-in support for common conventions, including multiple formats, subdomain/prefix restriction, content negotiation, versioning and much more.

Rustless in a port of Grape library from Ruby world. Based on hyper - an HTTP library for Rust.

Like Rust itself, Rustless is still in the early stages of development, so don't be surprised if APIs change and things break. If something's not working properly, file an issue or submit a pull request!

# Cargo.toml
[dependencies.rustless]
git = "https://github.com/rustless/rustless"

API docs

See also

Usage warning

Rustless is based on Iron, which is based on Hyper, which is synchronous. Hyper has a lot of limitations right now, and can't handle many simultaneous connections, especially with keep-alive. So it is highly recommended to use light asynchronous web server such as Nginx as a reverse proxy server with Rustless.

Basic Usage

Below is a simple example showing some of the more common features of Rustless.

#![feature(plugin)]#[plugin]externcrate rustless;externcrate hyper;externcrate iron;externcrate"rustc-serialize" as rustc_serialize;externcrate valico;use hyper::status::StatusCode;use iron::Iron;use rustless::{Application,Api,Nesting,Versioning};use rustc_serialize::json::ToJson;fnmain(){let api = Api::build(dsl!(|api| {// Specify API version
version("v1",Versioning::AcceptHeader("chat"));
prefix("api");// Create API for chats
mount(Api::build(dsl!(|chats_api| {
after(|client, _params| {
client.set_status(StatusCode::NotFound);Ok(())});// Add namespace
namespace("chats/:id", dsl!(|chat_ns| {// Valico settings for this namespace
params(|params| {
params.req_typed("id", valico::u64())});// Create endpoint for POST /chats/:id/users/:user_id
post("users/:user_id", dsl!(|endpoint| {// Add description
desc("Update user");// Valico settings for endpoint params
params(|params| {
params.req_typed("user_id", valico::u64());
params.req_typed("name", valico::string())});
handle(|client, params| {
client.json(&params.to_json())})}));}));})));}));let app = Application::new(api);Iron::new(app).listen("localhost:4000").unwrap();println!("On 4000");println!("Rustless server started!");}

Complex example

If you want to see how you can write come complex application using Rustless please see the example.

In that example please note these aspects:

  • Complex nested API with versioning.
  • CRUD operations with rust-postgres.
  • Swagger 2.0 intergration.
  • JSON Schema validations.
  • Error reporting.
  • Serializers.
  • File structure.
  • Integration with docopt.
  • Integration with deuterium-orm. Database migrations.

Mounting

In Rustless you can use three core entities to build your RESTful app: Api, Namespace and Endpoint.

  • Api can mount Api, Namespace and Endpoint
  • Namespace can mount Api, Namespace and Endpoint
Api::build(|api| {// Api inside Api example
api.mount(Api::build(dsl!(|nested_api| {// Endpoint definition
get("nested_info", dsl!|endpoint| {// endpoint.params(|params| {});// endpoint.desc("Some description");// Endpoint handler
handle(|client, _params| {
client.text("Some usefull info".to_string())})}));})))// The namespace method has a number of aliases, including: group,// resource, resources, and segment. Use whichever reads the best// for your API.
api.namespace("ns1", |ns1| {
ns1.group("ns2", |ns2| {
ns2.resource("ns3", |ns3| {
ns3.resources("ns4", |ns4| {
ns4.segment("ns5", |ns5| {// ...);})})})})})

Parameters validation and coercion

You can define validations and coercion options for your parameters using a DSL block inside Endpoint and Namespace definition. See Valico for more info about things you can do.

api.get("users/:user_id/messages/:message_id", |endpoint| {
endpoint.params(|params| {
params.req_typed("user_id",Valico::u64());
params.req_typed("message_id",Valico::u64());});// ...})

Use JSON Schema

Also you can use JSON Schema (IETF's draft v4) to validate your parameters. To use schemes in your application you need to make simple setup:

use valico::json_schema;use rustless::batteries::schemes;let scope = json_schema::Scope::new();// ... You can insert some external schemes here ...
schemes::enable_schemes(&mut app, scope).unwrap();

See Valico for more info about JSON Scheme usage inside DSL blocks.

Query strings

Rustless is intergated with queryst to allow smart query-string parsing end decoding (even with nesting, like foo[0][a]=a&foo[0][b]=b&foo[1][a]=aa&foo[1][b]=bb). See queryst for more info.

API versioning

There are three strategies in which clients can reach your API's endpoints:

  • Path
  • AcceptHeader
  • Param

Path versioning strategy

api.version("v1",Path);

Using this versioning strategy, clients should pass the desired version in the URL.

curl -H http://localhost:3000/v1/chats/

Header versioning strategy

api.version("v1",AcceptHeader("chat"));

Using this versioning strategy, clients should pass the desired version in the HTTP Accept head.

curl -H Accept:application/vnd.chat.v1+json http://localhost:3000/chats

Accept version format is the same as Github (uses)[https://developer.github.com/v3/media/].

Param versioning strategy

api.version("v1",Param("ver"));

Using this versioning strategy, clients should pass the desired version as a request parameter in the URL query.

curl -H http://localhost:9292/statuses/public_timeline?ver=v1

Respond with custom HTTP Status Code

By default Rustless returns a 200 status code for GET-Requests and 201 for POST-Requests. You can use status and set_status to query and set the actual HTTP Status Code

client.set_status(NotFound);

Use parameters

Request parameters are available through the params: JsonObject inside Endpoint handlers and all callbacks. This includes GET, POST and PUT parameters, along with any named parameters you specify in your route strings.

The request:

curl -d '{"text": "hello from echo"}' 'http://localhost:3000/echo' -H Content-Type:application/json -v

The Rustless endpoint:

api.post("", |endpoint| {
endpoint.handle(|client, params| {
client.json(params)})});

In the case of conflict between either of:

  • route string parameters
  • GET, POST and PUT parameters
  • the contents of the request body on POST and PUT

route string parameters will have precedence.

Redirecting

You can redirect to a new url temporarily (302) or permanently (301).

client.redirect("http://google.com");
client.redirect_permanent("http://google.com");

Errors firing

You can abort the execution of an API method by raising errors with error.

Define your error like this:

use rustless::errors::{Error,ErrorRefExt};#[deriving(Show)]pubstructUnauthorizedError;impl std::error::ErrorforUnauthorizedError{fndescription(&self) -> &str{return"UnauthorizedError";}}

And then throw:

client.error(UnauthorizedError);

Errors handling

By default Rustless wil respond all errors with status::InternalServerError.

Rustless can be told to rescue specific errors and return them in the custom API format.

api.error_formatter(|err, _media| {match err.downcast::<UnauthorizedError>(){Some(_) => {returnSome(Response::from_string(StatusCode::Unauthorized,"Please provide correct `token` parameter".to_string()))},None => None}});

Before and After callbacks

Blocks can be executed before or after every API call, using before, after, before_validation and after_validation.

Before and after callbacks execute in the following order:

  1. before
  2. before_validation
  3. validations
  4. after_validation
  5. the API call
  6. after

Steps 4, 5 and 6 only happen if validation succeeds.

The block applies to every API call within and below the current nesting level.

Secure API example

Api::build(dsl!(|api| {
prefix("api");
version("v1",Versioning::Path);
error_formatter(|err, _media| {match err.downcast::<UnauthorizedError>(){Some(_) => {returnSome(Response::from_string(StatusCode::Unauthorized,"Please provide correct `token` parameter".to_string()))},None => None}});
namespace("admin", dsl!(|admin_ns| {
params(|params| {
params.req_typed("token",Valico::string())});// Using after_validation callback to check token
after_validation(|&: _client, params| {match params.get("token"){// We can unwrap() safely because token in validated alreadySome(token) => if token.as_string().unwrap().as_slice() == "password1"{returnOk(())},None => ()}// Fire error from callback is token is wrongreturnErr(Box::new(UnauthorizedError)asBox<Error>)});// This `/api/admin/server_status` endpoint is secure now
get("server_status", dsl!(|endpoint| {
handle(|client, _params| {{let cookies = client.request.cookies();let signed_cookies = cookies.signed();let user_cookie = Cookie::new("session".to_string(),"verified".to_string());
signed_cookies.add(user_cookie);}
client.text("Everything is OK".to_string())})}));}))}))

JSON responses

Rustless includes JsonWay library to offer both complex JSON building DSL and configurable serializers for your objects. See API docs for details.

Also feel free to use any other serialization library you want.

Swagger 2.0

Rustless has a basic implementation of Swagger 2.0 specification. It is not fully complete and in future we need to implement:

  • JSON Schema support (when some appropriate JSON Schema library will appear);
  • Security parts of the specification;

But now you can already use Swagger 2.0:

letmut app = rustless::Application::new(rustless::Api::build(|api| {// ...
api.mount(swagger::create_api("api-docs"));// ...}))
swagger::enable(&mut app, swagger::Spec{info: swagger::Info{title:"Example API".to_string(),description:Some("Simple API to demonstration".to_string()),contact:Some(swagger::Contact{name:"Stanislav Panferov".to_string(),url:Some("http://panferov.me".to_string()),
..std::default::Default::default()}),license:Some(swagger::License{name:"MIT".to_string(),url:"http://opensource.org/licenses/MIT".to_string()}),
..std::default::Default::default()},host:"localhost:4000".to_string(),
..std::default::Default::default()});

After that you can use /api-docs path in Swagger UI to render your API structure.

Integration with PostgreSQL

We have an annotated example of such integration in postgres_example. Please try it and feel free to say your opinion.

Integration with Deuterium ORM

TODO: Example

About

REST-like API micro-framework for Rust. Works with Iron.

Resources

Contributing

Stars

0 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

Gitter

Table of Contents

What is Rustless?

Build Status

Rustless is a REST-like API micro-framework for Rust. It's designed to provide a simple DSL to easily develop RESTful APIs on top of the Iron web framework. It has built-in support for common conventions, including multiple formats, subdomain/prefix restriction, content negotiation, versioning and much more.

Rustless in a port of Grape library from Ruby world. Based on hyper - an HTTP library for Rust.

Like Rust itself, Rustless is still in the early stages of development, so don't be surprised if APIs change and things break. If something's not working properly, file an issue or submit a pull request!

# Cargo.toml
[dependencies.rustless]
git = "https://github.com/rustless/rustless"

API docs

See also

Usage warning

Rustless is based on Iron, which is based on Hyper, which is synchronous. Hyper has a lot of limitations right now, and can't handle many simultaneous connections, especially with keep-alive. So it is highly recommended to use light asynchronous web server such as Nginx as a reverse proxy server with Rustless.

Basic Usage

Below is a simple example showing some of the more common features of Rustless.

#![feature(plugin)]#[plugin]externcrate rustless;externcrate hyper;externcrate iron;externcrate"rustc-serialize" as rustc_serialize;externcrate valico;use hyper::status::StatusCode;use iron::Iron;use rustless::{Application,Api,Nesting,Versioning};use rustc_serialize::json::ToJson;fnmain(){let api = Api::build(dsl!(|api| {// Specify API version
version("v1",Versioning::AcceptHeader("chat"));
prefix("api");// Create API for chats
mount(Api::build(dsl!(|chats_api| {
after(|client, _params| {
client.set_status(StatusCode::NotFound);Ok(())});// Add namespace
namespace("chats/:id", dsl!(|chat_ns| {// Valico settings for this namespace
params(|params| {
params.req_typed("id", valico::u64())});// Create endpoint for POST /chats/:id/users/:user_id
post("users/:user_id", dsl!(|endpoint| {// Add description
desc("Update user");// Valico settings for endpoint params
params(|params| {
params.req_typed("user_id", valico::u64());
params.req_typed("name", valico::string())});
handle(|client, params| {
client.json(&params.to_json())})}));}));})));}));let app = Application::new(api);Iron::new(app).listen("localhost:4000").unwrap();println!("On 4000");println!("Rustless server started!");}

Complex example

If you want to see how you can write come complex application using Rustless please see the example.

In that example please note these aspects:

  • Complex nested API with versioning.
  • CRUD operations with rust-postgres.
  • Swagger 2.0 intergration.
  • JSON Schema validations.
  • Error reporting.
  • Serializers.
  • File structure.
  • Integration with docopt.
  • Integration with deuterium-orm. Database migrations.

Mounting

In Rustless you can use three core entities to build your RESTful app: Api, Namespace and Endpoint.

  • Api can mount Api, Namespace and Endpoint
  • Namespace can mount Api, Namespace and Endpoint
Api::build(|api| {// Api inside Api example
api.mount(Api::build(dsl!(|nested_api| {// Endpoint definition
get("nested_info", dsl!|endpoint| {// endpoint.params(|params| {});// endpoint.desc("Some description");// Endpoint handler
handle(|client, _params| {
client.text("Some usefull info".to_string())})}));})))// The namespace method has a number of aliases, including: group,// resource, resources, and segment. Use whichever reads the best// for your API.
api.namespace("ns1", |ns1| {
ns1.group("ns2", |ns2| {
ns2.resource("ns3", |ns3| {
ns3.resources("ns4", |ns4| {
ns4.segment("ns5", |ns5| {// ...);})})})})})

Parameters validation and coercion

You can define validations and coercion options for your parameters using a DSL block inside Endpoint and Namespace definition. See Valico for more info about things you can do.

api.get("users/:user_id/messages/:message_id", |endpoint| {
endpoint.params(|params| {
params.req_typed("user_id",Valico::u64());
params.req_typed("message_id",Valico::u64());});// ...})

Use JSON Schema

Also you can use JSON Schema (IETF's draft v4) to validate your parameters. To use schemes in your application you need to make simple setup:

use valico::json_schema;use rustless::batteries::schemes;let scope = json_schema::Scope::new();// ... You can insert some external schemes here ...
schemes::enable_schemes(&mut app, scope).unwrap();

See Valico for more info about JSON Scheme usage inside DSL blocks.

Query strings

Rustless is intergated with queryst to allow smart query-string parsing end decoding (even with nesting, like foo[0][a]=a&foo[0][b]=b&foo[1][a]=aa&foo[1][b]=bb). See queryst for more info.

API versioning

There are three strategies in which clients can reach your API's endpoints:

  • Path
  • AcceptHeader
  • Param

Path versioning strategy

api.version("v1",Path);

Using this versioning strategy, clients should pass the desired version in the URL.

curl -H http://localhost:3000/v1/chats/

Header versioning strategy

api.version("v1",AcceptHeader("chat"));

Using this versioning strategy, clients should pass the desired version in the HTTP Accept head.

curl -H Accept:application/vnd.chat.v1+json http://localhost:3000/chats

Accept version format is the same as Github (uses)[https://developer.github.com/v3/media/].

Param versioning strategy

api.version("v1",Param("ver"));

Using this versioning strategy, clients should pass the desired version as a request parameter in the URL query.

curl -H http://localhost:9292/statuses/public_timeline?ver=v1

Respond with custom HTTP Status Code

By default Rustless returns a 200 status code for GET-Requests and 201 for POST-Requests. You can use status and set_status to query and set the actual HTTP Status Code

client.set_status(NotFound);

Use parameters

Request parameters are available through the params: JsonObject inside Endpoint handlers and all callbacks. This includes GET, POST and PUT parameters, along with any named parameters you specify in your route strings.

The request:

curl -d '{"text": "hello from echo"}' 'http://localhost:3000/echo' -H Content-Type:application/json -v

The Rustless endpoint:

api.post("", |endpoint| {
endpoint.handle(|client, params| {
client.json(params)})});

In the case of conflict between either of:

  • route string parameters
  • GET, POST and PUT parameters
  • the contents of the request body on POST and PUT

route string parameters will have precedence.

Redirecting

You can redirect to a new url temporarily (302) or permanently (301).

client.redirect("http://google.com");
client.redirect_permanent("http://google.com");

Errors firing

You can abort the execution of an API method by raising errors with error.

Define your error like this:

use rustless::errors::{Error,ErrorRefExt};#[deriving(Show)]pubstructUnauthorizedError;impl std::error::ErrorforUnauthorizedError{fndescription(&self) -> &str{return"UnauthorizedError";}}

And then throw:

client.error(UnauthorizedError);

Errors handling

By default Rustless wil respond all errors with status::InternalServerError.

Rustless can be told to rescue specific errors and return them in the custom API format.

api.error_formatter(|err, _media| {match err.downcast::<UnauthorizedError>(){Some(_) => {returnSome(Response::from_string(StatusCode::Unauthorized,"Please provide correct `token` parameter".to_string()))},None => None}});

Before and After callbacks

Blocks can be executed before or after every API call, using before, after, before_validation and after_validation.

Before and after callbacks execute in the following order:

  1. before
  2. before_validation
  3. validations
  4. after_validation
  5. the API call
  6. after

Steps 4, 5 and 6 only happen if validation succeeds.

The block applies to every API call within and below the current nesting level.

Secure API example

Api::build(dsl!(|api| {
prefix("api");
version("v1",Versioning::Path);
error_formatter(|err, _media| {match err.downcast::<UnauthorizedError>(){Some(_) => {returnSome(Response::from_string(StatusCode::Unauthorized,"Please provide correct `token` parameter".to_string()))},None => None}});
namespace("admin", dsl!(|admin_ns| {
params(|params| {
params.req_typed("token",Valico::string())});// Using after_validation callback to check token
after_validation(|&: _client, params| {match params.get("token"){// We can unwrap() safely because token in validated alreadySome(token) => if token.as_string().unwrap().as_slice() == "password1"{returnOk(())},None => ()}// Fire error from callback is token is wrongreturnErr(Box::new(UnauthorizedError)asBox<Error>)});// This `/api/admin/server_status` endpoint is secure now
get("server_status", dsl!(|endpoint| {
handle(|client, _params| {{let cookies = client.request.cookies();let signed_cookies = cookies.signed();let user_cookie = Cookie::new("session".to_string(),"verified".to_string());
signed_cookies.add(user_cookie);}
client.text("Everything is OK".to_string())})}));}))}))

JSON responses

Rustless includes JsonWay library to offer both complex JSON building DSL and configurable serializers for your objects. See API docs for details.

Also feel free to use any other serialization library you want.

Swagger 2.0

Rustless has a basic implementation of Swagger 2.0 specification. It is not fully complete and in future we need to implement:

  • JSON Schema support (when some appropriate JSON Schema library will appear);
  • Security parts of the specification;

But now you can already use Swagger 2.0:

letmut app = rustless::Application::new(rustless::Api::build(|api| {// ...
api.mount(swagger::create_api("api-docs"));// ...}))
swagger::enable(&mut app, swagger::Spec{info: swagger::Info{title:"Example API".to_string(),description:Some("Simple API to demonstration".to_string()),contact:Some(swagger::Contact{name:"Stanislav Panferov".to_string(),url:Some("http://panferov.me".to_string()),
..std::default::Default::default()}),license:Some(swagger::License{name:"MIT".to_string(),url:"http://opensource.org/licenses/MIT".to_string()}),
..std::default::Default::default()},host:"localhost:4000".to_string(),
..std::default::Default::default()});

After that you can use /api-docs path in Swagger UI to render your API structure.

Integration with PostgreSQL

We have an annotated example of such integration in postgres_example. Please try it and feel free to say your opinion.

Integration with Deuterium ORM

TODO: Example

About

REST-like API micro-framework for Rust. Works with Iron.

Resources

Contributing

Stars

0 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages