{"id":13440232,"url":"https://github.com/rustless/rustless","last_synced_at":"2025-03-20T09:32:34.541Z","repository":{"id":21058827,"uuid":"24358038","full_name":"rustless/rustless","owner":"rustless","description":"REST-like API micro-framework for Rust. Works with Iron.","archived":false,"fork":false,"pushed_at":"2018-07-09T22:49:12.000Z","size":34370,"stargazers_count":618,"open_issues_count":18,"forks_count":50,"subscribers_count":17,"default_branch":"master","last_synced_at":"2025-02-04T11:44:40.442Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rustless.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-09-23T05:29:31.000Z","updated_at":"2025-01-21T04:59:31.000Z","dependencies_parsed_at":"2022-09-14T23:35:06.617Z","dependency_job_id":null,"html_url":"https://github.com/rustless/rustless","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rustless%2Frustless","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rustless%2Frustless/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rustless%2Frustless/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rustless%2Frustless/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rustless","download_url":"https://codeload.github.com/rustless/rustless/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244585926,"owners_count":20476841,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T03:01:20.915Z","updated_at":"2025-03-20T09:32:34.533Z","avatar_url":"https://github.com/rustless.png","language":"Rust","funding_links":[],"categories":["Libraries","Rust","库 Libraries","库","others"],"sub_categories":["Web programming","网络编程 Web programming","网页编程","web编程 Web programming"],"readme":"\n[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/rustless/rustless?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge)\n\n## Table of Contents\n\n- [What is Rustless?](#what-is-rustless)\n- [Usage warning](#usage-warning)\n- [Basic Usage](#basic-usage)\n- [Complex example](#complex-example)\n- [Mounting](#mounting)\n- [Parameters validation and coercion](#parameters-validation-and-coercion)\n- [Use JSON Schema](#use-json-schema)\n- [Query strings](#query-strings)\n- [API versioning](#api-versioning)\n- [Respond with custom HTTP Status Code](#respond-with-custom-http-status-code)\n- [Use parameters](#use-parameters)\n- [Redirecting](#redirecting)\n- [Errors firing](#errors-firing)\n- [Errors handling](#errors-handling)\n- [Before and After callbacks](#before-and-after-callbacks)\n- [Secure API example](#secure-api-example)\n- [JSON responses](#json-responses)\n- [Swagger 2.0 support](#swagger-20)\n- [Integration with PostgreSQL](#integration-with-postgresql)\n- [Integration with Deuterium ORM](#integration-with-deuterium-orm)\n\n## What is Rustless?\n\n[![Build Status](https://travis-ci.org/rustless/rustless.svg?branch=master)](https://travis-ci.org/rustless/rustless)\n\nRustless 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](https://github.com/iron/iron) web framework. It has built-in support for common conventions, including multiple formats, subdomain/prefix restriction, content negotiation, versioning and much more.\n\nRustless in a port of [Grape] library from Ruby world. Based on [hyper] - an HTTP library for Rust.\n\nLike 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!\n\n[Grape]: https://github.com/intridea/grape\n[hyper]: https://github.com/hyperium/hyper\n\n```toml\n# Cargo.toml\n[dependencies.rustless]\ngit = \"https://github.com/rustless/rustless\"\n```\n\n[API docs](http://rustless.org/rustless/doc/rustless)\n\n## See also\n\n* [Valico](https://github.com/rustless/valico) - Rust JSON validator and coercer. See [Api docs](http://rustless.org/valico/doc/valico).\n* [Queryst](https://github.com/rustless/queryst) - Rust query string parser with nesting support. See [Api docs](http://rustless.org/queryst/doc/queryst).\n* [JsonWay](https://github.com/rustless/jsonway) - JSON building DSL and configurable serializers for Rust. See [Api docs](http://rustless.org/jsonway/doc/jsonway).\n\n## Usage warning\n\nRustless 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.\n\n## Basic Usage\n\nBelow is a simple example showing some of the more common features of Rustless.\n\n~~~rust\nextern crate rustless;\nextern crate hyper;\nextern crate iron;\nextern crate rustc_serialize as serialize;\nextern crate valico;\n\nuse valico::json_dsl;\nuse rustless::server::status::StatusCode;\nuse rustless::{\n    Application, Api, Nesting, Versioning\n};\nuse rustless::json::ToJson;\n\nfn main() {\n\n    let api = Api::build(|api| {\n        // Specify API version\n        api.version(\"v1\", Versioning::AcceptHeader(\"chat\"));\n        api.prefix(\"api\");\n\n        // Create API for chats\n        api.mount(Api::build(|chats_api| {\n\n            chats_api.after(|client, _params| {\n                client.set_status(StatusCode::NotFound);\n                Ok(())\n            });\n\n            // Add namespace\n            chats_api.namespace(\"chats/:id\", |chat_ns| {\n\n                // Valico settings for this namespace\n                chat_ns.params(|params| {\n                    params.req_typed(\"id\", json_dsl::u64())\n                });\n\n                // Create endpoint for POST /chats/:id/users/:user_id\n                chat_ns.post(\"users/:user_id\", |endpoint| {\n\n                    // Add description\n                    endpoint.desc(\"Update user\");\n\n                    // Valico settings for endpoint params\n                    endpoint.params(|params| {\n                        params.req_typed(\"user_id\", json_dsl::u64());\n                        params.req_typed(\"id\", json_dsl::string())\n                    });\n\n                    endpoint.handle(|client, params| {\n                        client.json(\u0026params.to_json())\n                    })\n                });\n\n            });\n        }));\n    });\n\n    let app = Application::new(api);\n\n    iron::Iron::new(app).http(\"0.0.0.0:4000\").unwrap();\n    println!(\"On 4000\");\n\n    println!(\"Rustless server started!\");\n}\n~~~\n\nTo easily build the example, you can set your Cargo.toml file approximately as follows:\n\n~~~\n[package]\nname = \"rustless-example\"\nversion = \"0.1.0\"\n\n[dependencies]\nrustless = \"0.10.0\"\nhyper = \"0.10.5\"\nrustc-serialize = \"0.3\"\nvalico = \"1\"\n\n[dependencies.iron]\nversion = \"*\"\n~~~\n\n## Complex example\n\nIf you want to see how you can write some complex application using Rustless, please see the [example](https://github.com/rustless/rustless-example).\n\nIn the example, please note the following aspects:\n\n* Complex nested API with versioning.\n* CRUD operations with rust-postgres.\n* Swagger 2.0 intergration.\n* JSON Schema validations.\n* Error reporting.\n* Serializers.\n* File structure.\n* Integration with [docopt](https://github.com/docopt/docopt.rs).\n* Integration with [deuterium-orm](https://github.com/deuterium-orm/deuterium-orm). Database migrations.\n\n## Mounting\n\nIn Rustless you can use three core entities to build your RESTful app: `Api`, `Namespace` and `Endpoint`.\n\n* Api can mount Api, Namespace and Endpoint\n* Namespace can mount Api, Namespace and Endpoint\n\n~~~rust\nApi::build(|api| {\n\n    // Api inside Api example\n    api.mount(Api::build(|nested_api| {\n\n        // Endpoint definition\n        nested_api.get(\"nested_info\", |endpoint| {\n            // endpoint.params(|params| {});\n            // endpoint.desc(\"Some description\");\n\n            // Endpoint handler\n            endpoint.handle(|client, _params| {\n                client.text(\"Some usefull info\".to_string())\n            })\n        });\n\n    }))\n\n    // The namespace method has a number of aliases, including: group,\n    // resource, resources, and segment. Use whichever reads the best\n    // for your API.\n    api.namespace(\"ns1\", |ns1| {\n        ns1.group(\"ns2\", |ns2| {\n            ns2.resource(\"ns3\", |ns3| {\n                ns3.resources(\"ns4\", |ns4| {\n                    ns4.segment(\"ns5\", |ns5| {\n                        // ...\n                    );\n                })\n            })\n        })\n    })\n})\n~~~\n\n## Parameters validation and coercion\n\nYou can define validations and coercion options for your parameters using a DSL block inside `Endpoint` and `Namespace` definition. See [Valico] for more info about what you can do.\n\n~~~rust\napi.get(\"users/:user_id/messages/:message_id\", |endpoint| {\n    endpoint.params(|params| {\n        params.req_typed(\"user_id\", Valico::u64());\n        params.req_typed(\"message_id\", Valico::u64());\n    });\n\n    // ...\n})\n~~~\n\n## Use JSON Schema\n\nAlso you can use JSON Schema (IETF's draft v4) to validate your parameters. To use schemes in your application you need to use the following setup:\n\n~~~rust\nuse valico::json_schema;\nuse rustless::batteries::schemes;\n\nlet scope = json_schema::Scope::new();\n\n// ... You can insert some external schemes here ...\n\nschemes::enable_schemes(\u0026mut app, scope).unwrap();\n~~~\n\nSee [Valico] for more info about JSON Scheme usage inside DSL blocks.\n\n[Valico]: https://github.com/rustless/valico\n\n## Query strings\n\nRustless is intergated with [queryst] to allow smart query-string parsing\nend decoding (even with nesting, like `foo[0][a]=a\u0026foo[0][b]=b\u0026foo[1][a]=aa\u0026foo[1][b]=bb`). See [queryst] for more info.\n\n[queryst]: https://github.com/rustless/queryst\n\n## API versioning\n\nThere are three strategies in which clients can reach your API's endpoints:\n\n* Path\n* AcceptHeader\n* Param\n\n### Path versioning strategy\n\n~~~rust\napi.version(\"v1\", Path);\n~~~\n\nUsing this versioning strategy, clients should pass the desired version in the URL.\n\n    curl -H http://localhost:3000/v1/chats/\n\n### Header versioning strategy\n\n~~~rust\napi.version(\"v1\", AcceptHeader(\"chat\"));\n~~~\n\nUsing this versioning strategy, clients should pass the desired version in the HTTP `Accept` head.\n\n    curl -H Accept:application/vnd.chat.v1+json http://localhost:3000/chats\n\nAccept version format is the same as Github (uses)[https://developer.github.com/v3/media/].\n\n### Param versioning strategy\n\n~~~rust\napi.version(\"v1\", Param(\"ver\"));\n~~~\n\nUsing this versioning strategy, clients should pass the desired version as a request parameter in the URL query.\n\n    curl -H http://localhost:9292/statuses/public_timeline?ver=v1\n\n## Respond with custom HTTP Status Code\n\nBy 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\n\n~~~rust\nclient.set_status(NotFound);\n~~~\n\n## Use parameters\n\nRequest 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.\n\nThe request:\n\n~~~\ncurl -d '{\"text\": \"hello from echo\"}' 'http://localhost:3000/echo' -H Content-Type:application/json -v\n~~~\n\nThe Rustless endpoint:\n\n~~~rust\napi.post(\"\", |endpoint| {\n    endpoint.handle(|client, params| {\n        client.json(params)\n    })\n});\n~~~\n\nIn the case of conflict between either of:\n\n* route string parameters\n* `GET`, `POST` and `PUT` parameters\n* the contents of the request body on `POST` and `PUT`\n\nroute string parameters will have precedence.\n\n## Redirecting\n\nYou can redirect to a new url temporarily (302) or permanently (301).\n\n~~~rust\nclient.redirect(\"http://google.com\");\n~~~\n\n~~~rust\nclient.redirect_permanent(\"http://google.com\");\n~~~\n\n## Errors firing\n\nYou can abort the execution of an API method by raising errors with `error`.\n\nDefine your error like this:\n\n~~~rust\nuse rustless::errors::{Error, ErrorRefExt};\n\n#[deriving(Show)]\npub struct UnauthorizedError;\n\nimpl std::error::Error for UnauthorizedError {\n    fn description(\u0026self) -\u003e \u0026str {\n        return \"UnauthorizedError\";\n    }\n}\n~~~\n\nAnd then throw:\n\n~~~rust\nclient.error(UnauthorizedError);\n~~~\n\n## Errors handling\n\nBy default Rustless wil respond all errors with status::InternalServerError.\n\nRustless can be told to rescue specific errors and return them in the custom API format.\n\n~~~rust\napi.error_formatter(|err, _media| {\n    match err.downcast::\u003cUnauthorizedError\u003e() {\n        Some(_) =\u003e {\n            return Some(Response::from_string(StatusCode::Unauthorized, \"Please provide correct `token` parameter\".to_string()))\n        },\n        None =\u003e None\n    }\n});\n~~~\n\n## Before and After callbacks\n\nBlocks can be executed before or after every API call, using `before`, `after`,\n`before_validation` and `after_validation`.\n\nBefore and after callbacks execute in the following order:\n\n1. `before`\n2. `before_validation`\n3. _validations_\n4. `after_validation`\n5. _the API call_\n6. `after`\n\nSteps 4, 5 and 6 only happen if validation succeeds.\n\nThe block applies to every API call within and below the current nesting level.\n\n## Secure API example\n\n~~~rust\nApi::build(|api| {\n    api.prefix(\"api\");\n    api.version(\"v1\", Versioning::Path);\n\n    api.error_formatter(|err, _media| {\n        match err.downcast::\u003cUnauthorizedError\u003e() {\n            Some(_) =\u003e {\n                return Some(Response::from_string(StatusCode::Unauthorized, \"Please provide correct `token` parameter\".to_string()))\n            },\n            None =\u003e None\n        }\n    });\n\n    api.namespace(\"admin\", |admin_ns| {\n\n        admin_ns.params(|params| {\n            params.req_typed(\"token\", Valico::string())\n        });\n\n        // Using after_validation callback to check token\n        admin_ns.after_validation(|\u0026: _client, params| {\n\n            match params.get(\"token\") {\n                // We can unwrap() safely because token in validated already\n                Some(token) =\u003e if token.as_string().unwrap().as_slice() == \"password1\" { return Ok(()) },\n                None =\u003e ()\n            }\n\n            // Fire error from callback is token is wrong\n            return Err(Box::new(UnauthorizedError) as Box\u003cError\u003e)\n\n        });\n\n        // This `/api/admin/server_status` endpoint is secure now\n        admin_ns.get(\"server_status\", |endpoint| {\n            endpoint.handle(|client, _params| {\n                {\n                    let cookies = client.request.cookies();\n                    let signed_cookies = cookies.signed();\n\n                    let user_cookie = Cookie::new(\"session\".to_string(), \"verified\".to_string());\n                    signed_cookies.add(user_cookie);\n                }\n\n                client.text(\"Everything is OK\".to_string())\n            })\n        });\n    })\n})\n~~~\n\n## JSON responses\n\nRustless includes [JsonWay](https://github.com/rustless/jsonway) library to offer both complex JSON building DSL and configurable serializers for your objects. See [API docs](http://rustless.org/jsonway/doc/jsonway/) for details.\n\nAlso feel free to use any other serialization library you want.\n\n## Swagger 2.0\n\nRustless has a basic implementation of Swagger 2.0 specification. It is not fully complete and in future we need to implement:\n\n* JSON Schema support (when some appropriate JSON Schema library will appear);\n* Security parts of the specification;\n\nBut now you can already use Swagger 2.0:\n\n```rust\nlet mut app = rustless::Application::new(rustless::Api::build(|api| {\n    // ...\n\n    api.mount(swagger::create_api(\"api-docs\"));\n\n    // ...\n}))\n\nswagger::enable(\u0026mut app, swagger::Spec {\n    info: swagger::Info {\n        title: \"Example API\".to_string(),\n        description: Some(\"Simple API to demonstration\".to_string()),\n        contact: Some(swagger::Contact {\n            name: \"Stanislav Panferov\".to_string(),\n            url: Some(\"http://panferov.me\".to_string()),\n            ..std::default::Default::default()\n        }),\n        license: Some(swagger::License {\n            name: \"MIT\".to_string(),\n            url: \"http://opensource.org/licenses/MIT\".to_string()\n        }),\n        ..std::default::Default::default()\n    },\n    host: \"localhost:4000\".to_string(),\n    ..std::default::Default::default()\n});\n```\n\nAfter that you can use `/api-docs` path in Swagger UI to render your API structure.\n\n## Integration with PostgreSQL\n\nWe have an annotated example of such integration in [postgres_example](https://github.com/rustless/rustless/tree/master/examples/postgres). Please try it and feel free to say your opinion.\n\n## Integration with Deuterium ORM\n\nTODO: Example\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frustless%2Frustless","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frustless%2Frustless","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frustless%2Frustless/lists"}