{"id":14986718,"url":"https://github.com/ojford/tapioca","last_synced_at":"2025-10-16T15:11:33.311Z","repository":{"id":57669268,"uuid":"83492673","full_name":"OJFord/tapioca","owner":"OJFord","description":"Type-safe REST-focused HTTP client for Rust - via the OpenAPI Specification","archived":false,"fork":false,"pushed_at":"2022-12-10T12:49:45.000Z","size":206,"stargazers_count":14,"open_issues_count":0,"forks_count":4,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-10T09:00:37.151Z","etag":null,"topics":["oas","openapi","rest","rust","swagger","type-safety"],"latest_commit_sha":null,"homepage":"https://github.com/OJFord/tapioca/releases/download/v0.0.1/slides.pdf","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/OJFord.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-03-01T00:19:24.000Z","updated_at":"2022-12-10T12:49:50.000Z","dependencies_parsed_at":"2023-01-26T07:30:39.900Z","dependency_job_id":null,"html_url":"https://github.com/OJFord/tapioca","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OJFord%2Ftapioca","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OJFord%2Ftapioca/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OJFord%2Ftapioca/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OJFord%2Ftapioca/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/OJFord","download_url":"https://codeload.github.com/OJFord/tapioca/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248476481,"owners_count":21110291,"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":["oas","openapi","rest","rust","swagger","type-safety"],"created_at":"2024-09-24T14:13:24.695Z","updated_at":"2025-10-16T15:11:33.231Z","avatar_url":"https://github.com/OJFord.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"Tapioca\n=======\n\n_**T**yped **API**s (that **O**llie **C**oshed into an **A**cronym)_\n\n[![Crate](https://img.shields.io/crates/v/tapioca.svg)](https://crates.io/crates/tapioca)\n[![Build Status](https://travis-ci.org/OJFord/tapioca.svg?branch=master)](https://travis-ci.org/OJFord/tapioca)\n\n_tapioca_ is an HTTP client for _[rust](https://github.com/rust-lang/rust)_ that\naims to help the compiler help _you_ to access REST+JSON APIs in a type-safer\nmanner.\n\nIt uses the [OpenAPI Initiative's schema specification](https://github.com/OAI/OpenAPI-Specification)\nto infer types for path and query parameters, request and response bodies, et\nal. and then [serde](serde-rs/json) to de/serialise them.\n\n```rust\ninfer_api!(service, \"https://service.api/schema.yml\")\nuse service::path;\n\nfn main() {\n    let auth = service::ServerAuth::new();\n\n    match path::get(\u0026auth) {\n        Ok(response) =\u003e match response.body() {\n            path::OkBody::Status200(body) =\u003e println!(\"Thing is: {}\", body.thing),\n            path::OkBody::UnspecifiedCode(body) =\u003e {\n                // We're forced to handle every status code in the schema;\n                //  including the possibility that the server replies off-script.\n                println!(\"I don't know what thing is!\")\n            },\n        },\n        Err(response) =\u003e match response.body() {\n            path::ErrBody::Status403(body) =\u003e println!(\"That's not my thing\"),\n            path::ErrBody::UnspecifiedCode(_)\n            | path::ErrBody::MalformedJson(_)\n            | path::ErrBody::NetworkFailure() =\u003e println!(\"Something went wrong\"),\n        },\n    }\n}\n```\n\nSo, we can pattern-match responses by status code, and access the JSON response\nas a _rust_ type.\n\n_tapioca_ also aims to prevent you from shooting yourself in the foot with an\ninvalid _sequence_ of requests, such as '`GET` after `DELETE`' on a particular\nresource: this is achieved by constructing resource IDs only from responses,\nand static values. `DELETE` functions cause the resource ID argument to be\n_moved_ (while other methods only _borrow_) preventing it from being further\nused.\n\n## Getting started\n\nIn order to start using tapioca in your project, the first step is to locate the OAS schema for the API you wish to use. Let's assume it's at `https://example.org/schema.yml`. Then, add the latest version to your `Cargo.toml` as usual, and import tapioca with macros:\n```rust\n#[macro_use]\nextern crate tapioca;\n```\nand invoke the `infer_api` macro to build a client for the API:\n```rust\ninfer_api!(example, \"https://example.org/schema.yml\");\n```\n\nThe macro expands at compile-time, building a typed client in-place; (almost) all the code it generates will be located under a module named `example`, or whatever we specify in the first argument. The only exception is two crates (which must be loaded at the root level) which are needed to be externed inside your crate in order to use their macros - at least for now, the Rust's macro system is seeing a lot of change, and this may be improved. These are `serde_derive` and `tapicoa_codegen`; consequently, they also need to be in your `Cargo.toml`, but any other crates used (not for macros) by tapicoa will _not_ need this treatment, or pollute your project's namespace.\n\n## Accessing the client\n\nThe module built by `infer_api` contains modules with the names of each of the paths available on an API, and each of those contains a function for each of the HTTP methods valid for that resource. For example, to `GET /foobars`, the function ident is:\n```rust\nexample::foobars::get\n```\n\nIn order to call this function, we might need to supply some arguments for [authentication](#authentication), [query parameters](#query-parameters), [request body](#request-bodies), et al. - the types for these are located inside a module of the same name as the function, for example:\n```rust\nexample::foobars::get::QueryParams\n```\n\n## Authentication\n\nBefore we make a request, we need to introduce authentication - currently, authentication must be specified for every request, even if null.\n\nAuthentication requirements in an OAS schema are specified at two levels: server-wide, and operation specific - `GET /foobars` can have different requirements to other operations, which may just inherit from the server requirement. Thus we have two `enum`s of acceptable authentication schemes:\n```rust\nexample::ServerAuth\nexample::foobars::get::OpAuth\n```\nwhich must be used depends on whether the operation `examples::foobars::get` overrides the server-wide authentication requirement - but the type-checker will tell us if we get it wrong.\n\nIf there's no authentication required at all, we can just use:\n```rust\nexample::ServerAuth::new();\n```\n\nIf it's HTTP Basic, then (depending on whether it's a server or operation requirement):\n```rust\nexample::ServerAuth::Basic(username: String, password:String);\nexample::foobars::get::OpAuth::Basic(username: String, password:String);\n```\n\nIf it's a custom header:\n```rust\nexample::ServerAuth::ApiKey(api_key: String);\nexample::foobars::get::OpAuth::ApiKey(api_key: String);\n```\n\nThough note that the variant identifier, e.g. `Basic` or `ApiKey`, depends on the name used in the OAS schema. This is because there may be multiple definitions of the same type.\n\n## Making a request\n\nNow that we've seen how to construct an authentication argument, we can actually `GET` some `foobars`!\n\n```rust\nlet auth = examples::ServerAuth::new();\nlet response = examples::foobars::get(\u0026auth);\n```\n\n`response` is actually a `Result\u003cResponse, Response\u003e`: if the response status code is an error, we get an `Err(response)`, otherwise it's an `Ok(response)`. This means we can use `response.is_ok`, `response.is_err`, and pattern matching:\n```rust\nmatch examples::foobars::get(\u0026auth) {\n    Ok(response) =\u003e foobar_handler(response),\n    Err(response) =\u003e err_handler(response),\n}\n```\n\nWe can use further pattern matching in each of these handlers, in order to respond differently to different status codes:\n```rust\nfn foobar_handler(response: Response) {\n    match response.body {\n        OkBody::Status200(body) =\u003e {\n            for foobar in body.the_foobars {\n                println!(\"Foobar {} is named {}\", foobar.id, foobar.name);\n            }\n        },\n        OkBody::UnspecifiedCode(body)\n        | OkBody::MalformedJson(body) =\u003e something_else(),\n    }\n}\n```\nwhere we always have `UnspecifiedCode` (one not in the schema) and `MalformedJson` (invalid JSON, or did not match schema) as well as a `StatusXXX` for each of the possibilities specified in the schema. `err_handler` would look similar, with `ErrBody::Status403`, etc.\n\n## Request bodies\n\nSay this `example::foobars` collection also supports `POST`ing new `foobar`s, we can supply the request body to create one like this:\n```rust\nlet body = example::foobars::post::RequestBody {\n    name: \"Foobarry\".into(),\n    age: 12,\n    email: None,\n};\n```\n\nThe structure and field types of the body is fully defined by the schema, and may include:\n  - `i32`, `i64`\n  - `bool`\n  - `String`\n  - `Option\u003c_\u003e`\n  - `Vec\u003c_\u003e`\n  - further `struct`s\n\n## Query parameters\n\nQuery parameters are supplied much like [request bodies](#request-bodies):\n```rust\nlet query = example::foobars::get::QueryParams {\n    age: 34,\n};\n```\n\n## Path parameters\n\nPath parameters are slightly different. Because of the need to distinguish `example::foobars::get` from a `GET` on a single resource in that collection, the name of the path parameter is encoded in the path module name, for example:\n```rust\nexample::foobars__id_::get\n```\n\nIf the API specifies two resource identifiers in a row, this would be `foobars__id1___id2_`. This gets ugly, and may be changed in a future version.\n\nPath parameters can be constructed from the response, for example when creating a new resource and the server generated its ID, or from a static reference:\n```rust\nstatic provisioned_id = \"fea3c8e91baa1\";\n\nfn main() {\n    let auth = example::ServerAuth::new();\n    let resource = examples::foobars__id_::Resource_id::from_static(provisioned_id);\n\n    example::foobars__id_::get(\u0026resource, \u0026auth);\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fojford%2Ftapioca","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fojford%2Ftapioca","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fojford%2Ftapioca/lists"}