├── pretend ├── tests │ ├── builds │ │ ├── bodies.rs │ │ ├── headers.rs │ │ ├── attribute.rs │ │ ├── generics.rs │ │ ├── receivers.rs │ │ ├── requests.rs │ │ ├── non_method.rs │ │ ├── inconsistent_async.rs │ │ ├── non_method.stderr │ │ ├── attribute.stderr │ │ ├── generics.stderr │ │ ├── bodies.stderr │ │ ├── receivers.stderr │ │ ├── inconsistent_async.stderr │ │ ├── headers.stderr │ │ └── requests.stderr │ ├── builds-pre-1.54 │ │ ├── bodies.rs │ │ ├── headers.rs │ │ ├── attribute.rs │ │ ├── generics.rs │ │ ├── non_method.rs │ │ ├── receivers.rs │ │ ├── requests.rs │ │ ├── inconsistent_async.rs │ │ ├── non_method.stderr │ │ ├── attribute.stderr │ │ ├── generics.stderr │ │ ├── bodies.stderr │ │ ├── receivers.stderr │ │ ├── inconsistent_async.stderr │ │ ├── headers.stderr │ │ └── requests.stderr │ ├── build-sources │ │ ├── non_method.rs │ │ ├── bodies.rs │ │ ├── generics.rs │ │ ├── attribute.rs │ │ ├── inconsistent_async.rs │ │ ├── receivers.rs │ │ ├── headers.rs │ │ └── requests.rs │ ├── test_builds.rs │ ├── runtimes.rs │ ├── test_visibility.rs │ ├── test_blocking_local.rs │ ├── test_interceptors.rs │ ├── test_construction.rs │ ├── test_clients.rs │ ├── test_pretend.rs │ ├── server.rs │ ├── clients_tester.rs │ └── test_output.rs ├── examples │ ├── blocking.rs │ ├── headers.rs │ ├── templating.rs │ ├── queries.rs │ ├── methods.rs │ ├── interceptor.rs │ ├── bodies.rs │ └── responses.rs ├── Cargo.toml ├── src │ ├── resolver.rs │ ├── interceptor.rs │ ├── errors.rs │ ├── client.rs │ ├── internal.rs │ └── lib.rs └── README.md ├── .gitignore ├── tests ├── default-features-build │ ├── tests │ │ ├── test_builds.rs │ │ ├── builds │ │ │ ├── unsupported_client.rs │ │ │ └── unsupported_client.stderr │ │ └── test_no_https.rs │ ├── src │ │ └── lib.rs │ ├── Cargo.toml │ └── Cargo.lock ├── msrv-check │ ├── src │ │ └── lib.rs │ └── Cargo.toml └── doc-propagation │ ├── Cargo.toml │ └── src │ └── lib.rs ├── pretend-codegen ├── README.md ├── src │ ├── utils │ │ ├── withtokens.rs │ │ ├── single.rs │ │ └── attr.rs │ ├── method │ │ ├── attr.rs │ │ ├── query.rs │ │ ├── request.rs │ │ ├── checks.rs │ │ ├── headers.rs │ │ └── body.rs │ ├── utils.rs │ ├── attr.rs │ ├── format.rs │ ├── method.rs │ ├── errors.rs │ └── lib.rs └── Cargo.toml ├── pretend-awc ├── README.md ├── Cargo.toml └── src │ └── lib.rs ├── pretend-ureq ├── README.md ├── Cargo.toml └── src │ └── lib.rs ├── pretend-isahc ├── README.md ├── Cargo.toml └── src │ └── lib.rs ├── pretend-reqwest ├── README.md ├── Cargo.toml └── src │ ├── blocking.rs │ └── lib.rs ├── Cargo.toml ├── .github ├── release-drafter.yml ├── workflows │ ├── release-drafter.yml │ ├── repo.yml │ ├── publish.yml │ └── ci.yml └── labels.yml ├── README.md └── LICENSE /pretend/tests/builds/bodies.rs: -------------------------------------------------------------------------------- 1 | ../build-sources/bodies.rs -------------------------------------------------------------------------------- /pretend/tests/builds/headers.rs: -------------------------------------------------------------------------------- 1 | ../build-sources/headers.rs -------------------------------------------------------------------------------- /pretend/tests/builds/attribute.rs: -------------------------------------------------------------------------------- 1 | ../build-sources/attribute.rs -------------------------------------------------------------------------------- /pretend/tests/builds/generics.rs: -------------------------------------------------------------------------------- 1 | ../build-sources/generics.rs -------------------------------------------------------------------------------- /pretend/tests/builds/receivers.rs: -------------------------------------------------------------------------------- 1 | ../build-sources/receivers.rs -------------------------------------------------------------------------------- /pretend/tests/builds/requests.rs: -------------------------------------------------------------------------------- 1 | ../build-sources/requests.rs -------------------------------------------------------------------------------- /pretend/tests/builds-pre-1.54/bodies.rs: -------------------------------------------------------------------------------- 1 | ../build-sources/bodies.rs -------------------------------------------------------------------------------- /pretend/tests/builds-pre-1.54/headers.rs: -------------------------------------------------------------------------------- 1 | ../build-sources/headers.rs -------------------------------------------------------------------------------- /pretend/tests/builds/non_method.rs: -------------------------------------------------------------------------------- 1 | ../build-sources/non_method.rs -------------------------------------------------------------------------------- /pretend/tests/builds-pre-1.54/attribute.rs: -------------------------------------------------------------------------------- 1 | ../build-sources/attribute.rs -------------------------------------------------------------------------------- /pretend/tests/builds-pre-1.54/generics.rs: -------------------------------------------------------------------------------- 1 | ../build-sources/generics.rs -------------------------------------------------------------------------------- /pretend/tests/builds-pre-1.54/non_method.rs: -------------------------------------------------------------------------------- 1 | ../build-sources/non_method.rs -------------------------------------------------------------------------------- /pretend/tests/builds-pre-1.54/receivers.rs: -------------------------------------------------------------------------------- 1 | ../build-sources/receivers.rs -------------------------------------------------------------------------------- /pretend/tests/builds-pre-1.54/requests.rs: -------------------------------------------------------------------------------- 1 | ../build-sources/requests.rs -------------------------------------------------------------------------------- /pretend/tests/builds/inconsistent_async.rs: -------------------------------------------------------------------------------- 1 | ../build-sources/inconsistent_async.rs -------------------------------------------------------------------------------- /pretend/tests/builds-pre-1.54/inconsistent_async.rs: -------------------------------------------------------------------------------- 1 | ../build-sources/inconsistent_async.rs -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .idea 3 | Cargo.lock 4 | !tests/default-features-build/Cargo.lock 5 | !tests/msrv-check/Cargo.lock 6 | -------------------------------------------------------------------------------- /tests/default-features-build/tests/test_builds.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn test_builds() { 3 | let t = trybuild::TestCases::new(); 4 | t.compile_fail("tests/builds/*.rs"); 5 | } 6 | -------------------------------------------------------------------------------- /pretend-codegen/README.md: -------------------------------------------------------------------------------- 1 | # pretend code generator 2 | 3 | This proc-macro crate should be used with `pretend`. 4 | 5 | See [`pretend`](../pretend/README.md) for more information. 6 | -------------------------------------------------------------------------------- /pretend-awc/README.md: -------------------------------------------------------------------------------- 1 | # pretend awc client 2 | 3 | This crate provides a `awc` based client implementation for `pretend`. 4 | 5 | See [`pretend`](../pretend/README.md) for more information. 6 | -------------------------------------------------------------------------------- /pretend-ureq/README.md: -------------------------------------------------------------------------------- 1 | # pretend ureq client 2 | 3 | This crate provides a `ureq` based client implementation for `pretend`. 4 | 5 | See [`pretend`](../pretend/README.md) for more information. 6 | -------------------------------------------------------------------------------- /pretend-isahc/README.md: -------------------------------------------------------------------------------- 1 | # pretend isahc client 2 | 3 | This crate provides a `isahc` based client implementation for `pretend`. 4 | 5 | See [`pretend`](../pretend/README.md) for more information. 6 | -------------------------------------------------------------------------------- /pretend-reqwest/README.md: -------------------------------------------------------------------------------- 1 | # pretend reqwest client 2 | 3 | This crate provides a `reqwest` based client implementation for `pretend`. 4 | 5 | See [`pretend`](../pretend/README.md) for more information. 6 | -------------------------------------------------------------------------------- /tests/msrv-check/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Tests MSRV 2 | //! 3 | //! This crate tests if Pretend compiles with MSRV (Rust 1.44). 4 | //! It contains constraints on 3rd party crates to make them compile 5 | //! with MSRV. 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "pretend", 5 | "pretend-codegen", 6 | 7 | # Clients 8 | "pretend-awc", 9 | "pretend-isahc", 10 | "pretend-reqwest", 11 | "pretend-ureq", 12 | ] -------------------------------------------------------------------------------- /pretend/tests/build-sources/non_method.rs: -------------------------------------------------------------------------------- 1 | use pretend::pretend; 2 | 3 | #[pretend] 4 | trait Test { 5 | type Item; 6 | 7 | #[request(method = "GET", path = "/get")] 8 | async fn test(&self) -> Result<()>; 9 | 10 | } 11 | 12 | fn main() {} 13 | -------------------------------------------------------------------------------- /tests/doc-propagation/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pretend-test-doc-propagation" 3 | version = "0.0.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | pretend = { path = "../../pretend" } 8 | pretend-codegen = { path = "../../pretend-codegen" } 9 | 10 | [workspace] 11 | -------------------------------------------------------------------------------- /pretend/tests/build-sources/bodies.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use pretend::{pretend, Result}; 4 | 5 | #[pretend] 6 | trait Test { 7 | #[request(method = "GET", path = "/get")] 8 | async fn test_1(&self, body: String, json: String) -> Result<()>; 9 | } 10 | 11 | fn main() {} 12 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | categories: 2 | - title: "Added" 3 | labels: 4 | - feature 5 | - enhancement 6 | - title: "Fixed" 7 | label: bug 8 | change-template: '- $TITLE (#$NUMBER) @$AUTHOR' 9 | no-changes-template: '- No changes' 10 | template: | 11 | # Changed 12 | 13 | $CHANGES 14 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release drafter 2 | on: 3 | push: 4 | branches: [main] 5 | 6 | jobs: 7 | update-draft-release: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: toolmantim/release-drafter@v5 11 | env: 12 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 13 | 14 | -------------------------------------------------------------------------------- /pretend/tests/test_builds.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | #[cfg(not(tarpaulin))] 3 | fn test_builds() { 4 | let version = rustc_version::version().unwrap(); 5 | 6 | let t = trybuild::TestCases::new(); 7 | 8 | if version.minor < 54 { 9 | t.compile_fail("tests/builds-pre-1.54/*.rs"); 10 | } else { 11 | t.compile_fail("tests/builds/*.rs"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/doc-propagation/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Test crate 2 | 3 | #![forbid(missing_docs)] 4 | 5 | use pretend::{pretend, Result}; 6 | 7 | /// This REST endpoints tests documentation features 8 | #[pretend] 9 | trait TestTrait { 10 | /// Documentation for a simple method 11 | #[request(method = "GET", path = "/api/v1/test")] 12 | async fn test(&self) -> Result; 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/repo.yml: -------------------------------------------------------------------------------- 1 | name: Configure repository 2 | on: 3 | push: 4 | branches: [main] 5 | 6 | jobs: 7 | configure-labels: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | 12 | - uses: micnncim/action-label-syncer@v1 13 | with: 14 | token: ${{ secrets.GITHUB_TOKEN }} 15 | repository: ${{ github.repository }} 16 | -------------------------------------------------------------------------------- /pretend/tests/build-sources/generics.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use pretend::{pretend, Result}; 4 | 5 | #[pretend] 6 | trait Test { 7 | #[request(method = "GET", path = "/get")] 8 | async fn test_1(&self) -> Result<()>; 9 | #[request(method = "GET", path = "/get")] 10 | async fn test_2(&self) -> Result<()> 11 | where 12 | Self: Sized; 13 | } 14 | 15 | fn main() {} 16 | -------------------------------------------------------------------------------- /pretend-codegen/src/utils/withtokens.rs: -------------------------------------------------------------------------------- 1 | use quote::ToTokens; 2 | 3 | pub(crate) struct WithTokens<'a, V, T> 4 | where 5 | T: ToTokens, 6 | { 7 | pub(crate) value: V, 8 | pub(crate) tokens: &'a T, 9 | } 10 | 11 | impl<'a, V, T> WithTokens<'a, V, T> 12 | where 13 | T: ToTokens, 14 | { 15 | pub(crate) fn new(value: V, tokens: &'a T) -> Self { 16 | WithTokens { value, tokens } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /pretend/tests/build-sources/attribute.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use pretend::{pretend, Result}; 4 | 5 | #[pretend(local)] 6 | trait Test1 { 7 | #[request(method = "GET", path = "/get")] 8 | async fn test_1(&self) -> Result<()>; 9 | } 10 | 11 | #[pretend(blocking)] 12 | trait Test2 { 13 | #[request(method = "GET", path = "/get")] 14 | fn test_1(&self) -> Result<()>; 15 | } 16 | 17 | fn main() {} 18 | -------------------------------------------------------------------------------- /pretend/tests/runtimes.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | use tokio::runtime::Runtime; 3 | 4 | #[allow(unused)] 5 | pub fn create_runtime() -> Runtime { 6 | tokio::runtime::Builder::new_current_thread() 7 | .enable_all() 8 | .build() 9 | .unwrap() 10 | } 11 | 12 | #[allow(unused)] 13 | pub fn block_on(f: F) 14 | where 15 | F: Future, 16 | { 17 | create_runtime().block_on(f) 18 | } 19 | -------------------------------------------------------------------------------- /pretend/tests/builds-pre-1.54/non_method.stderr: -------------------------------------------------------------------------------- 1 | error: Failed to generate pretend implementation 2 | --> $DIR/non_method.rs:3:1 3 | | 4 | 3 | #[pretend] 5 | | ^^^^^^^^^^ 6 | | 7 | = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) 8 | 9 | error: Only methods are supported 10 | --> $DIR/non_method.rs:5:5 11 | | 12 | 5 | type Item; 13 | | ^^^^^^^^^^ 14 | -------------------------------------------------------------------------------- /pretend/tests/builds/non_method.stderr: -------------------------------------------------------------------------------- 1 | error: Failed to generate pretend implementation 2 | --> $DIR/non_method.rs:3:1 3 | | 4 | 3 | #[pretend] 5 | | ^^^^^^^^^^ 6 | | 7 | = note: this error originates in the attribute macro `pretend` (in Nightly builds, run with -Z macro-backtrace for more info) 8 | 9 | error: Only methods are supported 10 | --> $DIR/non_method.rs:5:5 11 | | 12 | 5 | type Item; 13 | | ^^^^^^^^^^ 14 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | on: 3 | release: 4 | types: [published] 5 | 6 | jobs: 7 | publish: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | 12 | - name: Install cargo workspace 13 | run: cargo install cargo-workspaces 14 | 15 | - name: Cargo login 16 | run: cargo login ${{ secrets.CARGO_TOKEN }} 17 | 18 | - name: Publish 19 | run: cargo workspaces publish --from-git --skip-published 20 | -------------------------------------------------------------------------------- /pretend-codegen/src/method/attr.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::{parse_name_value_2_attr, WithTokens}; 2 | use syn::Attribute; 3 | 4 | pub(crate) fn parse_request_attr( 5 | attr: &Attribute, 6 | ) -> Option, Attribute>> { 7 | parse_name_value_2_attr(attr, "request", "method", "path") 8 | } 9 | 10 | pub(crate) fn parse_header_attr( 11 | attr: &Attribute, 12 | ) -> Option, Attribute>> { 13 | parse_name_value_2_attr(attr, "header", "name", "value") 14 | } 15 | -------------------------------------------------------------------------------- /pretend-codegen/src/utils.rs: -------------------------------------------------------------------------------- 1 | mod attr; 2 | mod single; 3 | mod withtokens; 4 | 5 | pub(crate) use attr::parse_name_value_2_attr; 6 | pub(crate) use single::Single; 7 | pub(crate) use withtokens::WithTokens; 8 | 9 | use proc_macro2::Ident; 10 | use syn::{FnArg, Pat}; 11 | 12 | pub(crate) fn parse_param_name(input: &FnArg) -> Option<&Ident> { 13 | match input { 14 | FnArg::Typed(param) => match &*param.pat { 15 | Pat::Ident(pat) => Some(&pat.ident), 16 | _ => None, 17 | }, 18 | _ => None, 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /pretend/tests/build-sources/inconsistent_async.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use pretend::{pretend, Result}; 4 | 5 | #[pretend] 6 | trait Test1 {} 7 | 8 | #[pretend] 9 | trait Test2 { 10 | #[request(method = "GET", path = "/get")] 11 | fn test_1(&self) -> Result<()>; 12 | #[request(method = "GET", path = "/get")] 13 | async fn test_2(&self) -> Result<()>; 14 | } 15 | 16 | #[pretend(?Send)] 17 | trait Test3 { 18 | #[request(method = "GET", path = "/get")] 19 | fn test_1(&self) -> Result<()>; 20 | } 21 | 22 | fn main() {} 23 | -------------------------------------------------------------------------------- /pretend/tests/build-sources/receivers.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use pretend::{pretend, Result}; 4 | 5 | #[pretend] 6 | trait Test { 7 | #[request(method = "GET", path = "/get")] 8 | async fn test_1() -> Result<()>; 9 | #[request(method = "GET", path = "/get")] 10 | async fn test_2(self) -> Result<()>; 11 | #[request(method = "GET", path = "/get")] 12 | async fn test_3(&mut self) -> Result<()>; 13 | #[request(method = "GET", path = "/get")] 14 | async fn test_4(input: i32) -> Result<()>; 15 | } 16 | 17 | fn main() {} 18 | -------------------------------------------------------------------------------- /tests/default-features-build/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Tests for default features 2 | //! 3 | //! This crate tests if the default features (with Send + Sync errors) works well. 4 | //! It compiles all client crates that supports errors with Send and Sync bounds, 5 | //! to verify that they effectively supports these bounds. It also compiles a test 6 | //! client with an error that does not support these bounds, to make sure that it 7 | //! fails to compile. 8 | //! 9 | //! It also test if, by default, pretend-* crates are compiled without any feature 10 | //! in the client crate. 11 | -------------------------------------------------------------------------------- /pretend/tests/build-sources/headers.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use pretend::{pretend, Result}; 4 | 5 | #[pretend] 6 | trait Test { 7 | #[request(method = "GET", path = "/get")] 8 | #[header(value = "test")] 9 | async fn test_1(&self) -> Result<()>; 10 | #[request(method = "GET", path = "/get")] 11 | #[header(name = "X-Test")] 12 | async fn test_2(&self) -> Result<()>; 13 | #[request(method = "GET", path = "/get")] 14 | #[header(name = "X-Test", value = "test", other = "something")] 15 | async fn test_3(&self) -> Result<()>; 16 | } 17 | 18 | fn main() {} 19 | -------------------------------------------------------------------------------- /pretend/tests/builds-pre-1.54/attribute.stderr: -------------------------------------------------------------------------------- 1 | error: Expected `#[pretend]` or `#[pretend(?Send)]` 2 | --> $DIR/attribute.rs:5:1 3 | | 4 | 5 | #[pretend(local)] 5 | | ^^^^^^^^^^^^^^^^^ 6 | | 7 | = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) 8 | 9 | error: Expected `#[pretend]` or `#[pretend(?Send)]` 10 | --> $DIR/attribute.rs:11:1 11 | | 12 | 11 | #[pretend(blocking)] 13 | | ^^^^^^^^^^^^^^^^^^^^ 14 | | 15 | = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) 16 | -------------------------------------------------------------------------------- /pretend/tests/builds/attribute.stderr: -------------------------------------------------------------------------------- 1 | error: Expected `#[pretend]` or `#[pretend(?Send)]` 2 | --> $DIR/attribute.rs:5:1 3 | | 4 | 5 | #[pretend(local)] 5 | | ^^^^^^^^^^^^^^^^^ 6 | | 7 | = note: this error originates in the attribute macro `pretend` (in Nightly builds, run with -Z macro-backtrace for more info) 8 | 9 | error: Expected `#[pretend]` or `#[pretend(?Send)]` 10 | --> $DIR/attribute.rs:11:1 11 | | 12 | 11 | #[pretend(blocking)] 13 | | ^^^^^^^^^^^^^^^^^^^^ 14 | | 15 | = note: this error originates in the attribute macro `pretend` (in Nightly builds, run with -Z macro-backtrace for more info) 16 | -------------------------------------------------------------------------------- /pretend/tests/builds-pre-1.54/generics.stderr: -------------------------------------------------------------------------------- 1 | error: Failed to generate pretend implementation 2 | --> $DIR/generics.rs:5:1 3 | | 4 | 5 | #[pretend] 5 | | ^^^^^^^^^^ 6 | | 7 | = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) 8 | 9 | error: Generics are not supported 10 | --> $DIR/generics.rs:8:20 11 | | 12 | 8 | async fn test_1(&self) -> Result<()>; 13 | | ^^^ 14 | 15 | error: Generics are not supported 16 | --> $DIR/generics.rs:11:5 17 | | 18 | 11 | / where 19 | 12 | | Self: Sized; 20 | | |___________________^ 21 | -------------------------------------------------------------------------------- /pretend/tests/builds/generics.stderr: -------------------------------------------------------------------------------- 1 | error: Failed to generate pretend implementation 2 | --> $DIR/generics.rs:5:1 3 | | 4 | 5 | #[pretend] 5 | | ^^^^^^^^^^ 6 | | 7 | = note: this error originates in the attribute macro `pretend` (in Nightly builds, run with -Z macro-backtrace for more info) 8 | 9 | error: Generics are not supported 10 | --> $DIR/generics.rs:8:20 11 | | 12 | 8 | async fn test_1(&self) -> Result<()>; 13 | | ^^^ 14 | 15 | error: Generics are not supported 16 | --> $DIR/generics.rs:11:5 17 | | 18 | 11 | / where 19 | 12 | | Self: Sized; 20 | | |___________________^ 21 | -------------------------------------------------------------------------------- /pretend/tests/build-sources/requests.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use pretend::{pretend, Result}; 4 | 5 | #[pretend] 6 | trait Test { 7 | async fn test_1(&self) -> Result<()>; 8 | #[request(method = "GET", path = "/get")] 9 | #[request(method = "GET", path = "/get")] 10 | async fn test_2(&self) -> Result<()>; 11 | #[request(method = "GET")] 12 | async fn test_3(&self) -> Result<()>; 13 | #[request(path = "/get")] 14 | async fn test_4(&self) -> Result<()>; 15 | #[request(method = "GET", path = "/get", other = "something")] 16 | async fn test_5(&self) -> Result<()>; 17 | } 18 | 19 | fn main() {} 20 | -------------------------------------------------------------------------------- /pretend/examples/blocking.rs: -------------------------------------------------------------------------------- 1 | use pretend::{pretend, Pretend, Result, Url}; 2 | use pretend_reqwest::BlockingClient; 3 | 4 | // This example show how the use of a blocking client 5 | 6 | #[pretend] 7 | trait HttpBin { 8 | #[request(method = "GET", path = "/get")] 9 | fn get(&self) -> Result; 10 | } 11 | 12 | fn create_pretend() -> impl HttpBin { 13 | let url = Url::parse("https://httpbin.org").unwrap(); 14 | Pretend::for_client(BlockingClient::default()).with_url(url) 15 | } 16 | 17 | fn main() { 18 | let pretend = create_pretend(); 19 | 20 | let get = pretend.get().unwrap(); 21 | println!("{}", get); 22 | } 23 | -------------------------------------------------------------------------------- /pretend-codegen/src/method/query.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::parse_param_name; 2 | use proc_macro2::TokenStream; 3 | use quote::quote; 4 | use syn::TraitItemMethod; 5 | 6 | pub(crate) fn implement_query(method: &TraitItemMethod) -> TokenStream { 7 | if has_query(method) { 8 | quote! { 9 | let url = pretend::internal::build_query(url, &query)?; 10 | } 11 | } else { 12 | TokenStream::new() 13 | } 14 | } 15 | 16 | fn has_query(method: &TraitItemMethod) -> bool { 17 | let inputs = &method.sig.inputs; 18 | inputs 19 | .iter() 20 | .filter_map(parse_param_name) 21 | .any(|param| param == "query") 22 | } 23 | -------------------------------------------------------------------------------- /tests/default-features-build/tests/builds/unsupported_client.rs: -------------------------------------------------------------------------------- 1 | use pretend::client::{BlockingClient, Bytes, Method}; 2 | use pretend::{Error, HeaderMap, Response, Result, Url}; 3 | use std::rc::Rc; 4 | use thiserror::Error; 5 | 6 | #[derive(Default, Debug, Error)] 7 | #[error("Test error")] 8 | struct TestError { 9 | data: Rc, 10 | } 11 | 12 | struct TestClient; 13 | 14 | impl BlockingClient for TestClient { 15 | fn execute( 16 | &self, 17 | _: Method, 18 | _: Url, 19 | _: HeaderMap, 20 | _: Option, 21 | ) -> Result> { 22 | Err(Error::response(TestError::default())) 23 | } 24 | } 25 | 26 | fn main() {} -------------------------------------------------------------------------------- /pretend-ureq/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pretend-ureq" 3 | edition = "2018" 4 | version = "0.4.0" 5 | description = "ureq based client for pretend." 6 | authors = ["Lucien XU "] 7 | license = "MIT" 8 | homepage = "https://github.com/SfietKonstantin/pretend" 9 | documentation = "https://docs.rs/pretend-ureq/latest/pretend_ureq/" 10 | repository = "https://github.com/SfietKonstantin/pretend" 11 | keywords = ["http", "client", "web", "declarative"] 12 | categories = ["web-programming::http-client"] 13 | readme = "README.md" 14 | 15 | [dependencies] 16 | pretend = { path = "../pretend", version = "0.4.0" } 17 | ureq = { version = "2", default-features = false } 18 | 19 | [features] 20 | default = ["ureq/default"] 21 | -------------------------------------------------------------------------------- /tests/default-features-build/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pretend-test-default-feature-build" 3 | version = "0.0.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | pretend = { path = "../../pretend" } 8 | pretend-codegen = { path = "../../pretend-codegen" } 9 | 10 | pretend-isahc = { path = "../../pretend-isahc", default-features = false } 11 | pretend-reqwest = { path = "../../pretend-reqwest", default-features = false } 12 | pretend-ureq = { path = "../../pretend-ureq", default-features = false } 13 | 14 | [dev-dependencies] 15 | anyhow = "1.0" 16 | thiserror = "1.0" 17 | tokio = { version = "1.5", features = ["macros", "rt-multi-thread"] } 18 | trybuild = "1.0" 19 | ureq = { version = "=2.1.1", default-features = false } 20 | 21 | [workspace] 22 | -------------------------------------------------------------------------------- /pretend-isahc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pretend-isahc" 3 | edition = "2018" 4 | version = "0.4.0" 5 | description = "isahc based client for pretend." 6 | authors = ["Lucien XU "] 7 | license = "MIT" 8 | homepage = "https://github.com/SfietKonstantin/pretend" 9 | documentation = "https://docs.rs/pretend-isahc/latest/pretend_isahc/" 10 | repository = "https://github.com/SfietKonstantin/pretend" 11 | keywords = ["http", "client", "web", "async", "declarative"] 12 | categories = ["web-programming::http-client"] 13 | readme = "README.md" 14 | 15 | [dependencies] 16 | pretend = { path = "../pretend", version = "0.4.0" } 17 | isahc = { version = "1", default-features = false } 18 | 19 | [features] 20 | default = ["isahc/default"] 21 | -------------------------------------------------------------------------------- /pretend-awc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pretend-awc" 3 | edition = "2018" 4 | version = "0.4.0" 5 | description = "awc based client for pretend." 6 | authors = ["Lucien XU "] 7 | license = "MIT" 8 | homepage = "https://github.com/SfietKonstantin/pretend" 9 | documentation = "https://docs.rs/pretend-awc/latest/pretend_awc/" 10 | repository = "https://github.com/SfietKonstantin/pretend" 11 | keywords = ["http", "client", "web", "async", "declarative"] 12 | categories = ["web-programming::http-client"] 13 | readme = "README.md" 14 | 15 | [dependencies] 16 | pretend = { path = "../pretend", version = "0.4.0", features = ["local-error"] } 17 | awc = { version = "2", default-features = false } 18 | 19 | [features] 20 | default = ["awc/default"] 21 | -------------------------------------------------------------------------------- /pretend-codegen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pretend-codegen" 3 | edition = "2018" 4 | version = "0.4.0" 5 | description = "codegen for pretend." 6 | authors = ["Lucien XU "] 7 | license = "MIT" 8 | homepage = "https://github.com/SfietKonstantin/pretend" 9 | documentation = "https://docs.rs/pretend-codegen/latest/pretend_codegen/" 10 | repository = "https://github.com/SfietKonstantin/pretend" 11 | keywords = ["http", "client", "web", "async", "declarative"] 12 | categories = ["web-programming::http-client"] 13 | readme = "README.md" 14 | 15 | [lib] 16 | proc-macro = true 17 | 18 | [dependencies] 19 | http = "0.2" 20 | lazy_static = "1.4" 21 | proc-macro2 = "1.0" 22 | quote = "1.0" 23 | regex = "1.5" 24 | syn = { version = "1.0", features = ["full"] } 25 | -------------------------------------------------------------------------------- /pretend/examples/headers.rs: -------------------------------------------------------------------------------- 1 | use pretend::{pretend, Pretend, Result, Url}; 2 | use pretend_reqwest::Client; 3 | 4 | // This example show how to send various headers to https://httpbin.org 5 | 6 | #[pretend] 7 | trait HttpBin { 8 | #[request(method = "GET", path = "/get")] 9 | #[header(name = "X-Test", value = "Hello")] 10 | #[header(name = "X-Something-Nice", value = "Lovely")] 11 | async fn get(&self) -> Result; 12 | } 13 | 14 | fn create_pretend() -> impl HttpBin { 15 | let url = Url::parse("https://httpbin.org").unwrap(); 16 | Pretend::for_client(Client::default()).with_url(url) 17 | } 18 | 19 | #[tokio::main] 20 | async fn main() { 21 | let pretend = create_pretend(); 22 | 23 | let result = pretend.get().await.unwrap(); 24 | println!("{}", result); 25 | } 26 | -------------------------------------------------------------------------------- /pretend-reqwest/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pretend-reqwest" 3 | edition = "2018" 4 | version = "0.4.0" 5 | description = "reqwest based client for pretend." 6 | authors = ["Lucien XU "] 7 | license = "MIT" 8 | homepage = "https://github.com/SfietKonstantin/pretend" 9 | documentation = "https://docs.rs/pretend-reqwest/latest/pretend_reqwest/" 10 | repository = "https://github.com/SfietKonstantin/pretend" 11 | keywords = ["http", "client", "web", "async", "declarative"] 12 | categories = ["web-programming::http-client"] 13 | readme = "README.md" 14 | 15 | [dependencies] 16 | pretend = { path = "../pretend", version = "0.4.0" } 17 | reqwest = { version = "0.11", default-features = false } 18 | 19 | [features] 20 | default = ["reqwest/default"] 21 | blocking = ["reqwest/blocking"] 22 | -------------------------------------------------------------------------------- /pretend/tests/test_visibility.rs: -------------------------------------------------------------------------------- 1 | mod runtimes; 2 | mod server; 3 | 4 | use self::api::TestApi; 5 | use pretend::{Pretend, Url}; 6 | use pretend_reqwest::Client; 7 | 8 | mod api { 9 | use pretend::{pretend, Result}; 10 | 11 | #[pretend] 12 | pub trait TestApi { 13 | #[request(method = "GET", path = "/method")] 14 | async fn get(&self) -> Result; 15 | } 16 | } 17 | 18 | fn new_client() -> impl TestApi { 19 | let url = Url::parse(server::URL).unwrap(); 20 | Pretend::for_client(Client::default()).with_url(url) 21 | } 22 | 23 | #[test] 24 | fn pretend_generates_pub_visibility() { 25 | server::test(|| { 26 | runtimes::block_on(async { 27 | let result = new_client().get().await; 28 | assert!(result.is_ok()); 29 | }) 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /.github/labels.yml: -------------------------------------------------------------------------------- 1 | - name: breaking 2 | description: Requires breaking backwards compatibility 3 | color: c8571e 4 | - name: bug 5 | description: Something is borken 6 | color: b3292b 7 | - name: ci 8 | description: Continuous integration 9 | color: b7dee7 10 | - name: dependencies 11 | description: Dependency update 12 | color: b7dee7 13 | - name: enhancement 14 | description: Make a feature better 15 | color: 64b5f6 16 | - name: feature 17 | description: A new feature 18 | color: 81c784 19 | - name: good first issue 20 | description: Something not too complex 21 | color: ffd54f 22 | 23 | # meta 24 | - name: blocked 25 | description: "" 26 | color: e0e0e0 27 | - name: duplicate 28 | description: "" 29 | color: e0e0e0 30 | - name: invalid 31 | description: "" 32 | color: e0e0e0 33 | - name: wontfix 34 | description: "" 35 | color: e0e0e0 36 | -------------------------------------------------------------------------------- /pretend-codegen/src/utils/single.rs: -------------------------------------------------------------------------------- 1 | use std::iter::FromIterator; 2 | 3 | pub(crate) enum Single { 4 | None, 5 | Single(T), 6 | TooMany(Vec), 7 | } 8 | 9 | impl FromIterator for Single { 10 | fn from_iter(iter: I) -> Self 11 | where 12 | I: IntoIterator, 13 | { 14 | let mut iter = iter.into_iter(); 15 | 16 | let item1 = iter.next(); 17 | let item2 = iter.next(); 18 | 19 | match (item1, item2) { 20 | (None, None) => Self::None, 21 | (Some(item), None) => Self::Single(item), 22 | (item1, item2) => { 23 | let iter1 = item1.into_iter(); 24 | let iter2 = item2.into_iter(); 25 | let all = iter1.chain(iter2).chain(iter).collect(); 26 | Self::TooMany(all) 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /pretend-codegen/src/attr.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::INVALID_ATTR; 2 | use proc_macro2::Span; 3 | use syn::parse::{Error, Parse, ParseStream, Result}; 4 | use syn::Token; 5 | 6 | pub(crate) struct PretendAttr { 7 | pub local: bool, 8 | } 9 | 10 | mod kw { 11 | syn::custom_keyword!(Send); 12 | } 13 | 14 | impl Parse for PretendAttr { 15 | fn parse(input: ParseStream) -> Result { 16 | match try_parse(input) { 17 | Ok(args) if input.is_empty() => Ok(args), 18 | _ => Err(Error::new(Span::call_site(), INVALID_ATTR)), 19 | } 20 | } 21 | } 22 | 23 | fn try_parse(input: ParseStream) -> Result { 24 | if input.peek(Token![?]) { 25 | input.parse::()?; 26 | input.parse::()?; 27 | Ok(PretendAttr { local: true }) 28 | } else { 29 | Ok(PretendAttr { local: false }) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/msrv-check/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pretend-msrv-check" 3 | version = "0.0.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | pretend = { path = "../../pretend" } 8 | pretend-codegen = { path = "../../pretend-codegen" } 9 | 10 | pretend-awc = { path = "../../pretend-awc" } 11 | pretend-isahc = { path = "../../pretend-isahc", default-features = false } 12 | pretend-reqwest = { path = "../../pretend-reqwest", default-features = false } 13 | pretend-ureq = { path = "../../pretend-ureq", default-features = false } 14 | 15 | # Enforce MSRV 16 | bitflags = "=1.2.1" 17 | futures-task = "=0.3.11" 18 | h2 = "=0.3.12" 19 | http = "=0.2.4" 20 | hyper = "=0.14.4" 21 | indexmap = "=1.5.2" 22 | ipnet = "=2.5.1" 23 | isahc = "=1.3.1" 24 | libz-sys = "=1.0.27" 25 | linked-hash-map = "=0.5.4" 26 | once_cell = "=1.14.0" 27 | openssl-sys = "=0.9.70" 28 | polling = "=2.5.1" 29 | tokio = "=1.14.1" 30 | ureq = "=2.2.0" 31 | url = "=2.2.2" 32 | 33 | [workspace] 34 | -------------------------------------------------------------------------------- /pretend/tests/builds-pre-1.54/bodies.stderr: -------------------------------------------------------------------------------- 1 | error: Failed to generate pretend implementation 2 | --> $DIR/bodies.rs:5:1 3 | | 4 | 5 | #[pretend] 5 | | ^^^^^^^^^^ 6 | | 7 | = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) 8 | 9 | error: Method can only have at most one body parameter 10 | --> $DIR/bodies.rs:8:5 11 | | 12 | 8 | async fn test_1(&self, body: String, json: String) -> Result<()>; 13 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 14 | 15 | error: Body parameter defined here 16 | --> $DIR/bodies.rs:8:28 17 | | 18 | 8 | async fn test_1(&self, body: String, json: String) -> Result<()>; 19 | | ^^^^ 20 | 21 | error: Body parameter defined here 22 | --> $DIR/bodies.rs:8:42 23 | | 24 | 8 | async fn test_1(&self, body: String, json: String) -> Result<()>; 25 | | ^^^^ 26 | -------------------------------------------------------------------------------- /pretend/tests/builds/bodies.stderr: -------------------------------------------------------------------------------- 1 | error: Failed to generate pretend implementation 2 | --> $DIR/bodies.rs:5:1 3 | | 4 | 5 | #[pretend] 5 | | ^^^^^^^^^^ 6 | | 7 | = note: this error originates in the attribute macro `pretend` (in Nightly builds, run with -Z macro-backtrace for more info) 8 | 9 | error: Method can only have at most one body parameter 10 | --> $DIR/bodies.rs:8:5 11 | | 12 | 8 | async fn test_1(&self, body: String, json: String) -> Result<()>; 13 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 14 | 15 | error: Body parameter defined here 16 | --> $DIR/bodies.rs:8:28 17 | | 18 | 8 | async fn test_1(&self, body: String, json: String) -> Result<()>; 19 | | ^^^^ 20 | 21 | error: Body parameter defined here 22 | --> $DIR/bodies.rs:8:42 23 | | 24 | 8 | async fn test_1(&self, body: String, json: String) -> Result<()>; 25 | | ^^^^ 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pretend 2 | 3 | [![Build](https://github.com/SfietKonstantin/pretend/workflows/ci/badge.svg)](https://github.com/SfietKonstantin/pretend/actions) 4 | [![codecov](https://codecov.io/gh/SfietKonstantin/pretend/branch/main/graph/badge.svg)](https://codecov.io/gh/SfietKonstantin/pretend) 5 | 6 | `pretend` is a modular, [Feign]-inspired HTTP, client based on macros. It's goal is to decouple 7 | the definition of a REST API from it's implementation. 8 | 9 | 10 | Some features: 11 | - Declarative 12 | - Asynchronous-first implementations 13 | - HTTP client agnostic 14 | - JSON support thanks to serde 15 | 16 | [Feign]: https://github.com/OpenFeign/feign 17 | 18 | This repository contains the code for [`pretend`](pretend/README.md) and 19 | [`pretend-codegen`](pretend-codegen/README.md) as well as [`pretend-reqwest`](pretend-reqwest/README.md) 20 | [`pretend-ishac`](pretend-isahc/README.md) and [`pretend-awc`](pretend-awc/README.md). 21 | 22 | MSRV for the `pretend` ecosystem is Rust **1.44**. 23 | -------------------------------------------------------------------------------- /pretend/examples/templating.rs: -------------------------------------------------------------------------------- 1 | use pretend::{pretend, Pretend, Result, Url}; 2 | use pretend_reqwest::Client; 3 | 4 | // This example show how to use templating to customize paths and headers 5 | 6 | #[pretend] 7 | trait HttpBin { 8 | #[request(method = "GET", path = "/{path}")] 9 | #[header(name = "X-{header}", value = "{first}-{second}")] 10 | #[header(name = "X-Test", value = "{value}")] 11 | async fn get( 12 | &self, 13 | path: &str, 14 | header: &str, 15 | first: i32, 16 | second: i32, 17 | value: &str, 18 | ) -> Result; 19 | } 20 | 21 | fn create_pretend() -> impl HttpBin { 22 | let url = Url::parse("https://httpbin.org").unwrap(); 23 | Pretend::for_client(Client::default()).with_url(url) 24 | } 25 | 26 | #[tokio::main] 27 | async fn main() { 28 | let pretend = create_pretend(); 29 | 30 | let future = pretend.get("get", "Header", 1, 2, "something"); 31 | let result = future.await.unwrap(); 32 | println!("{}", result); 33 | } 34 | -------------------------------------------------------------------------------- /pretend/tests/builds-pre-1.54/receivers.stderr: -------------------------------------------------------------------------------- 1 | error: Failed to generate pretend implementation 2 | --> $DIR/receivers.rs:5:1 3 | | 4 | 5 | #[pretend] 5 | | ^^^^^^^^^^ 6 | | 7 | = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) 8 | 9 | error: Method must take `&self` as receiver 10 | --> $DIR/receivers.rs:8:5 11 | | 12 | 8 | async fn test_1() -> Result<()>; 13 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 14 | 15 | error: Method must take `&self` as receiver 16 | --> $DIR/receivers.rs:10:21 17 | | 18 | 10 | async fn test_2(self) -> Result<()>; 19 | | ^^^^ 20 | 21 | error: Method must take `&self` as receiver 22 | --> $DIR/receivers.rs:12:21 23 | | 24 | 12 | async fn test_3(&mut self) -> Result<()>; 25 | | ^^^^^^^^^ 26 | 27 | error: Method must take `&self` as receiver 28 | --> $DIR/receivers.rs:14:5 29 | | 30 | 14 | async fn test_4(input: i32) -> Result<()>; 31 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 32 | -------------------------------------------------------------------------------- /pretend/tests/builds/receivers.stderr: -------------------------------------------------------------------------------- 1 | error: Failed to generate pretend implementation 2 | --> $DIR/receivers.rs:5:1 3 | | 4 | 5 | #[pretend] 5 | | ^^^^^^^^^^ 6 | | 7 | = note: this error originates in the attribute macro `pretend` (in Nightly builds, run with -Z macro-backtrace for more info) 8 | 9 | error: Method must take `&self` as receiver 10 | --> $DIR/receivers.rs:8:5 11 | | 12 | 8 | async fn test_1() -> Result<()>; 13 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 14 | 15 | error: Method must take `&self` as receiver 16 | --> $DIR/receivers.rs:10:21 17 | | 18 | 10 | async fn test_2(self) -> Result<()>; 19 | | ^^^^ 20 | 21 | error: Method must take `&self` as receiver 22 | --> $DIR/receivers.rs:12:21 23 | | 24 | 12 | async fn test_3(&mut self) -> Result<()>; 25 | | ^^^^^^^^^ 26 | 27 | error: Method must take `&self` as receiver 28 | --> $DIR/receivers.rs:14:5 29 | | 30 | 14 | async fn test_4(input: i32) -> Result<()>; 31 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 32 | -------------------------------------------------------------------------------- /pretend/tests/builds-pre-1.54/inconsistent_async.stderr: -------------------------------------------------------------------------------- 1 | error: Please declare at least one method for this trait 2 | --> $DIR/inconsistent_async.rs:6:7 3 | | 4 | 6 | trait Test1 {} 5 | | ^^^^^ 6 | 7 | error: Unable to deduce if this trait is async or not. Please mark either all methods or none as async. 8 | --> $DIR/inconsistent_async.rs:9:7 9 | | 10 | 9 | trait Test2 { 11 | | ^^^^^ 12 | 13 | error: async method defined here 14 | --> $DIR/inconsistent_async.rs:13:5 15 | | 16 | 13 | async fn test_2(&self) -> Result<()>; 17 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 18 | 19 | error: non-async method defined here 20 | --> $DIR/inconsistent_async.rs:11:5 21 | | 22 | 11 | fn test_1(&self) -> Result<()>; 23 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 24 | 25 | error: `?Send` is not supported for blocking implementation 26 | --> $DIR/inconsistent_async.rs:16:1 27 | | 28 | 16 | #[pretend(?Send)] 29 | | ^^^^^^^^^^^^^^^^^ 30 | | 31 | = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) 32 | -------------------------------------------------------------------------------- /pretend/tests/builds/inconsistent_async.stderr: -------------------------------------------------------------------------------- 1 | error: Please declare at least one method for this trait 2 | --> $DIR/inconsistent_async.rs:6:7 3 | | 4 | 6 | trait Test1 {} 5 | | ^^^^^ 6 | 7 | error: Unable to deduce if this trait is async or not. Please mark either all methods or none as async. 8 | --> $DIR/inconsistent_async.rs:9:7 9 | | 10 | 9 | trait Test2 { 11 | | ^^^^^ 12 | 13 | error: async method defined here 14 | --> $DIR/inconsistent_async.rs:13:5 15 | | 16 | 13 | async fn test_2(&self) -> Result<()>; 17 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 18 | 19 | error: non-async method defined here 20 | --> $DIR/inconsistent_async.rs:11:5 21 | | 22 | 11 | fn test_1(&self) -> Result<()>; 23 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 24 | 25 | error: `?Send` is not supported for blocking implementation 26 | --> $DIR/inconsistent_async.rs:16:1 27 | | 28 | 16 | #[pretend(?Send)] 29 | | ^^^^^^^^^^^^^^^^^ 30 | | 31 | = note: this error originates in the attribute macro `pretend` (in Nightly builds, run with -Z macro-backtrace for more info) 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 SfietKonstantin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pretend/tests/test_blocking_local.rs: -------------------------------------------------------------------------------- 1 | mod runtimes; 2 | mod server; 3 | 4 | use pretend::{pretend, Pretend, Result, Url}; 5 | use pretend_reqwest::BlockingClient; 6 | use pretend_reqwest::Client; 7 | 8 | #[pretend(?Send)] 9 | trait TestApiLocal { 10 | #[request(method = "GET", path = "/method")] 11 | async fn get(&self) -> Result; 12 | } 13 | 14 | #[pretend] 15 | trait TestApiBlocking { 16 | #[request(method = "GET", path = "/method")] 17 | fn get(&self) -> Result; 18 | } 19 | 20 | #[test] 21 | fn pretend_with_local_and_blocking() { 22 | let url = Url::parse(server::URL).unwrap(); 23 | 24 | server::test(|| { 25 | runtimes::block_on(async { 26 | let client = Pretend::for_client(Client::default()).with_url(url.clone()); 27 | let result = TestApiLocal::get(&client).await; 28 | assert!(result.is_ok()); 29 | }); 30 | 31 | { 32 | let client = Pretend::for_client(BlockingClient::default()).with_url(url.clone()); 33 | let result = TestApiBlocking::get(&client); 34 | assert!(result.is_ok()); 35 | } 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /pretend-codegen/src/method/request.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::{ 2 | ErrorsExt, INVALID_REQUEST, MISSING_REQUEST, TOO_MANY_REQUESTS, TOO_MANY_REQUESTS_HINT, 3 | }; 4 | use crate::method::parse_request_attr; 5 | use crate::utils::Single; 6 | use syn::{Error, Result, TraitItemMethod}; 7 | 8 | pub(crate) fn get_request(method: &TraitItemMethod) -> Result<(String, String)> { 9 | let attrs = &method.attrs; 10 | let single = attrs 11 | .iter() 12 | .filter_map(parse_request_attr) 13 | .collect::>(); 14 | 15 | match single { 16 | Single::None => Err(Error::new_spanned(method, MISSING_REQUEST)), 17 | Single::Single(item) => { 18 | let value = item.value; 19 | let tokens = item.tokens; 20 | value.ok_or_else(|| Error::new_spanned(tokens, INVALID_REQUEST)) 21 | } 22 | Single::TooMany(requests) => { 23 | let errors = requests 24 | .into_iter() 25 | .map(|item| Error::new_spanned(item.tokens, TOO_MANY_REQUESTS_HINT)) 26 | .collect::>(); 27 | 28 | errors.into_result(|| Error::new_spanned(method, TOO_MANY_REQUESTS)) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /pretend/examples/queries.rs: -------------------------------------------------------------------------------- 1 | use pretend::{pretend, Pretend, Result, Url}; 2 | use pretend_reqwest::Client; 3 | use serde::Serialize; 4 | 5 | // This example show how to pass URL queries to https://httpbin.org 6 | 7 | #[derive(Clone, Serialize)] 8 | struct Query { 9 | first: String, 10 | second: i32, 11 | } 12 | 13 | #[pretend] 14 | trait HttpBin { 15 | #[request(method = "GET", path = "/get?first={first}&second={second}")] 16 | async fn get(&self, first: String, second: i32) -> Result; 17 | 18 | #[request(method = "GET", path = "/get")] 19 | async fn get_query(&self, query: Query) -> Result; 20 | } 21 | 22 | fn create_pretend() -> impl HttpBin { 23 | let url = Url::parse("https://httpbin.org").unwrap(); 24 | Pretend::for_client(Client::default()).with_url(url) 25 | } 26 | 27 | #[tokio::main] 28 | async fn main() { 29 | let pretend = create_pretend(); 30 | 31 | let result = pretend.get("Hello".to_string(), 123).await.unwrap(); 32 | println!("{}", result); 33 | 34 | let query = Query { 35 | first: "Hello".to_string(), 36 | second: 123, 37 | }; 38 | 39 | let result = pretend.get_query(query).await.unwrap(); 40 | println!("{}", result); 41 | } 42 | -------------------------------------------------------------------------------- /pretend/examples/methods.rs: -------------------------------------------------------------------------------- 1 | use pretend::{pretend, Pretend, Result, Url}; 2 | use pretend_reqwest::Client; 3 | 4 | // This example show how to send HTTP requests to https://httpbin.org 5 | // using different methods 6 | 7 | #[pretend] 8 | trait HttpBin { 9 | #[request(method = "GET", path = "/get")] 10 | async fn get(&self) -> Result; 11 | 12 | #[request(method = "POST", path = "/post")] 13 | async fn post(&self) -> Result; 14 | 15 | #[request(method = "PUT", path = "/put")] 16 | async fn put(&self) -> Result; 17 | 18 | #[request(method = "DELETE", path = "/delete")] 19 | async fn delete(&self) -> Result; 20 | } 21 | 22 | fn create_pretend() -> impl HttpBin { 23 | let url = Url::parse("https://httpbin.org").unwrap(); 24 | Pretend::for_client(Client::default()).with_url(url) 25 | } 26 | 27 | #[tokio::main] 28 | async fn main() { 29 | let pretend = create_pretend(); 30 | 31 | let get = pretend.get().await.unwrap(); 32 | println!("{}", get); 33 | 34 | let post = pretend.post().await.unwrap(); 35 | println!("{}", post); 36 | 37 | let put = pretend.post().await.unwrap(); 38 | println!("{}", put); 39 | 40 | let delete = pretend.post().await.unwrap(); 41 | println!("{}", delete); 42 | } 43 | -------------------------------------------------------------------------------- /pretend/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pretend" 3 | edition = "2018" 4 | version = "0.4.0" 5 | description = "pretend is a modular, Feign-inspired, HTTP client based on macros." 6 | authors = ["Lucien XU "] 7 | license = "MIT" 8 | homepage = "https://github.com/SfietKonstantin/pretend" 9 | documentation = "https://docs.rs/pretend/latest/pretend/" 10 | repository = "https://github.com/SfietKonstantin/pretend" 11 | keywords = ["http", "client", "web", "async", "declarative"] 12 | categories = ["web-programming::http-client"] 13 | readme = "README.md" 14 | 15 | [dependencies] 16 | async-trait = "0.1" 17 | bytes = "1.0" 18 | encoding_rs = "0.8" 19 | http = "0.2" 20 | mime = "0.3" 21 | pretend-codegen = { path = "../pretend-codegen", version = "0.4.0" } 22 | serde = "1.0" 23 | serde_json = "1.0" 24 | serde_urlencoded = "0.7" 25 | thiserror = "1.0" 26 | url = "2.2" 27 | 28 | [dev-dependencies] 29 | actix-web = "3.3" 30 | pretend-awc = { path = "../pretend-awc" } 31 | pretend-isahc = { path = "../pretend-isahc" } 32 | pretend-reqwest = { path = "../pretend-reqwest", features = ["blocking"] } 33 | pretend-ureq = { path = "../pretend-ureq" } 34 | rustc_version = "0.2" 35 | tokio = { version = "1.5", features = ["macros", "rt-multi-thread"] } 36 | trybuild = "1.0" 37 | 38 | [features] 39 | default = [] 40 | local-error = [] 41 | -------------------------------------------------------------------------------- /pretend/tests/test_interceptors.rs: -------------------------------------------------------------------------------- 1 | mod runtimes; 2 | mod server; 3 | 4 | use self::api::TestApi; 5 | use pretend::http::HeaderValue; 6 | use pretend::interceptor::{InterceptRequest, Request}; 7 | use pretend::{Pretend, Result, Url}; 8 | use pretend_reqwest::Client; 9 | 10 | mod api { 11 | use pretend::{pretend, Json, Result}; 12 | use std::collections::HashMap; 13 | 14 | #[pretend] 15 | pub trait TestApi { 16 | #[request(method = "GET", path = "/headers")] 17 | async fn headers(&self) -> Result>>; 18 | } 19 | } 20 | 21 | struct AuthInterceptor; 22 | 23 | impl InterceptRequest for AuthInterceptor { 24 | fn intercept(&self, mut request: Request) -> Result { 25 | let value = HeaderValue::from_static("Bearer abcde"); 26 | request.headers.append("Authorization", value); 27 | Ok(request) 28 | } 29 | } 30 | 31 | fn new_client() -> impl TestApi { 32 | let url = Url::parse(server::URL).unwrap(); 33 | Pretend::for_client(Client::default()) 34 | .with_url(url) 35 | .with_request_interceptor(AuthInterceptor) 36 | } 37 | 38 | #[test] 39 | fn pretend_interceptor_modifies_header() { 40 | server::test(|| { 41 | runtimes::block_on(async { 42 | let result = new_client().headers().await.unwrap(); 43 | let headers = result.value(); 44 | assert_eq!(headers.get("authorization").unwrap(), "Bearer abcde"); 45 | }) 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /tests/default-features-build/tests/test_no_https.rs: -------------------------------------------------------------------------------- 1 | use pretend::{pretend, Pretend, Result, Url}; 2 | 3 | #[pretend] 4 | trait HttpBin { 5 | #[request(method = "GET", path = "/get")] 6 | async fn get(&self) -> Result<()>; 7 | } 8 | 9 | #[pretend] 10 | trait HttpBinSync { 11 | #[request(method = "GET", path = "/get")] 12 | fn get(&self) -> Result<()>; 13 | } 14 | 15 | #[tokio::test] 16 | async fn test_reqwest_no_https_feature() { 17 | // With default-features = false 18 | // reqwest will not use any TLS crate, 19 | // and thus, cannot handle https calls 20 | 21 | let url = Url::parse("https://httpbin.org").unwrap(); 22 | let pretend = Pretend::for_client(pretend_reqwest::Client::default()).with_url(url); 23 | let result = pretend.get().await; 24 | 25 | let error = anyhow::Error::from(result.unwrap_err()); 26 | assert!(format!("{:?}", error).contains("scheme is not http")); 27 | } 28 | 29 | #[test] 30 | fn test_ureq_no_https_feature() { 31 | // With default-features = false 32 | // ureq will not use any TLS crate, 33 | // and thus, cannot handle https calls 34 | 35 | let url = Url::parse("https://httpbin.org").unwrap(); 36 | let pretend = 37 | Pretend::for_client(pretend_ureq::Client::new(pretend_ureq::ureq::agent())).with_url(url); 38 | let result = pretend.get(); 39 | 40 | let error = anyhow::Error::from(result.unwrap_err()); 41 | assert!(format!("{:?}", error).contains("ureq was build without HTTP support")); 42 | } 43 | -------------------------------------------------------------------------------- /pretend-reqwest/src/blocking.rs: -------------------------------------------------------------------------------- 1 | use pretend::client::{BlockingClient as PBlockingClient, Bytes, Method}; 2 | use pretend::{Error, HeaderMap, Response as PResponse, Result, Url}; 3 | use reqwest::blocking::Client; 4 | use std::mem; 5 | 6 | /// `reqwest` based `pretend` blocking client 7 | #[derive(Clone, Debug, Default)] 8 | pub struct BlockingClient { 9 | client: Client, 10 | } 11 | 12 | impl BlockingClient { 13 | /// Constructor with custom client 14 | /// 15 | /// This constructor creates a client implementation 16 | /// for `pretend` wrapping the supplied `reqwest` client. 17 | pub fn new(client: Client) -> Self { 18 | BlockingClient { client } 19 | } 20 | } 21 | 22 | impl PBlockingClient for BlockingClient { 23 | fn execute( 24 | &self, 25 | method: Method, 26 | url: Url, 27 | headers: HeaderMap, 28 | body: Option, 29 | ) -> Result> { 30 | let mut builder = self.client.request(method, url).headers(headers); 31 | if let Some(body) = body { 32 | builder = builder.body(body); 33 | } 34 | let response = builder.send(); 35 | let mut response = response.map_err(Error::response)?; 36 | 37 | let status = response.status(); 38 | let headers = mem::take(response.headers_mut()); 39 | 40 | let bytes = response.bytes(); 41 | let bytes = bytes.map_err(Error::body)?; 42 | 43 | Ok(PResponse::new(status, headers, bytes)) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /pretend-codegen/src/format.rs: -------------------------------------------------------------------------------- 1 | use lazy_static::lazy_static; 2 | use proc_macro2::{Ident, Span, TokenStream}; 3 | use quote::quote; 4 | use regex::Regex; 5 | 6 | pub(crate) fn format(string: String, param: &str) -> TokenStream { 7 | let param = Ident::new(param, Span::call_site()); 8 | let params = find_params(&string); 9 | if params.is_empty() { 10 | quote! { 11 | let #param = #string; 12 | } 13 | } else { 14 | let params = params 15 | .into_iter() 16 | .map(|param| Ident::new(param, Span::call_site())) 17 | .collect::>(); 18 | 19 | quote! { 20 | let #param = format!(#string, #(#params=#params,)*); 21 | let #param = #param.as_str(); 22 | } 23 | } 24 | } 25 | 26 | lazy_static! { 27 | static ref PARAM_RE: Regex = Regex::new(r"\{([^}]+)\}").unwrap(); 28 | } 29 | 30 | fn find_params(path: &str) -> Vec<&str> { 31 | PARAM_RE 32 | .captures_iter(path) 33 | .filter_map(|cap| cap.get(1)) 34 | .map(|m| m.as_str()) 35 | .collect() 36 | } 37 | 38 | #[cfg(test)] 39 | mod tests { 40 | use super::*; 41 | 42 | #[test] 43 | fn test_find_params() { 44 | let path = "/{user}/{id}"; 45 | let params = find_params(path); 46 | assert_eq!(params, vec!["user", "id"]); 47 | } 48 | 49 | #[test] 50 | fn test_find_no_params() { 51 | let path = "/{}"; 52 | let params = find_params(path); 53 | assert_eq!(params, Vec::<&str>::new()); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /pretend/examples/interceptor.rs: -------------------------------------------------------------------------------- 1 | use pretend::http::header::AUTHORIZATION; 2 | use pretend::http::HeaderValue; 3 | use pretend::interceptor::{InterceptRequest, Request}; 4 | use pretend::{pretend, Error, Pretend, Result, Url}; 5 | use pretend_reqwest::Client; 6 | 7 | // This example show how to use an interceptor to override headers 8 | 9 | #[pretend] 10 | trait HttpBin { 11 | #[request(method = "GET", path = "/get")] 12 | async fn get(&self) -> Result; 13 | } 14 | 15 | struct AuthInterceptor { 16 | auth: String, 17 | } 18 | 19 | impl AuthInterceptor { 20 | fn new(auth: String) -> Self { 21 | AuthInterceptor { auth } 22 | } 23 | } 24 | 25 | impl InterceptRequest for AuthInterceptor { 26 | fn intercept(&self, mut request: Request) -> Result { 27 | // Create the header, reporting failure if the header is invalid 28 | let header = format!("Bearer {}", self.auth); 29 | let header = HeaderValue::from_str(&header).map_err(|err| Error::Request(Box::new(err)))?; 30 | 31 | // Set the authorization header in the request 32 | request.headers.append(AUTHORIZATION, header); 33 | Ok(request) 34 | } 35 | } 36 | 37 | fn create_pretend() -> impl HttpBin { 38 | let url = Url::parse("https://httpbin.org").unwrap(); 39 | Pretend::for_client(Client::default()) 40 | .with_url(url) 41 | .with_request_interceptor(AuthInterceptor::new("test".to_string())) 42 | } 43 | 44 | #[tokio::main] 45 | async fn main() { 46 | let pretend = create_pretend(); 47 | 48 | let future = pretend.get(); 49 | let result = future.await.unwrap(); 50 | println!("{}", result); 51 | } 52 | -------------------------------------------------------------------------------- /pretend-codegen/src/method/checks.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::{UNSUPPORTED_GENERICS, UNSUPPORTED_RECEIVER}; 2 | use syn::{Error, FnArg, Receiver, Result, Signature, TraitItemMethod}; 3 | 4 | pub(crate) fn check_no_generics(method: &TraitItemMethod) -> Result<()> { 5 | let sig = &method.sig; 6 | check_no_generic_params(sig)?; 7 | check_no_where_clause(sig)?; 8 | Ok(()) 9 | } 10 | fn check_no_generic_params(sig: &Signature) -> Result<()> { 11 | if sig.generics.params.is_empty() { 12 | Ok(()) 13 | } else { 14 | Err(Error::new_spanned(&sig.generics, UNSUPPORTED_GENERICS)) 15 | } 16 | } 17 | 18 | fn check_no_where_clause(sig: &Signature) -> Result<()> { 19 | if let Some(where_clause) = sig.generics.where_clause.as_ref() { 20 | Err(Error::new_spanned(where_clause, UNSUPPORTED_GENERICS)) 21 | } else { 22 | Ok(()) 23 | } 24 | } 25 | 26 | pub(crate) fn check_correct_receiver(method: &TraitItemMethod) -> Result<()> { 27 | let receiver = get_receiver(method); 28 | let receiver = receiver.ok_or_else(|| Error::new_spanned(&method.sig, UNSUPPORTED_RECEIVER))?; 29 | get_good_mutability(receiver).ok_or_else(|| Error::new_spanned(receiver, UNSUPPORTED_RECEIVER)) 30 | } 31 | 32 | fn get_receiver(method: &TraitItemMethod) -> Option<&Receiver> { 33 | let sig = &method.sig; 34 | let first_arg = sig.inputs.first()?; 35 | 36 | match first_arg { 37 | FnArg::Receiver(receiver) => Some(receiver), 38 | _ => None, 39 | } 40 | } 41 | 42 | fn get_good_mutability(receiver: &Receiver) -> Option<()> { 43 | let (_, lifetime) = receiver.reference.as_ref()?; 44 | if lifetime.is_none() && receiver.mutability.is_none() { 45 | Some(()) 46 | } else { 47 | None 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /pretend-codegen/src/method/headers.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::{Report, INVALID_HEADER, METHOD_FAILURE}; 2 | use crate::format::format; 3 | use crate::method::parse_header_attr; 4 | use crate::utils::WithTokens; 5 | use proc_macro2::TokenStream; 6 | use quote::quote; 7 | use syn::{Attribute, Error, Result, TraitItemMethod}; 8 | 9 | pub(crate) fn implement_headers(method: &TraitItemMethod) -> Result { 10 | let attrs = &method.attrs; 11 | let headers = attrs 12 | .iter() 13 | .filter_map(parse_header_attr) 14 | .collect::>(); 15 | 16 | let implem = if headers.is_empty() { 17 | quote! { 18 | let headers = pretend::HeaderMap::new(); 19 | } 20 | } else { 21 | let headers = headers 22 | .into_iter() 23 | .map(implement_header_result) 24 | .collect::>() 25 | .into_result(|| Error::new_spanned(method, METHOD_FAILURE))?; 26 | 27 | quote! { 28 | let mut headers = pretend::HeaderMap::new(); 29 | #(#headers)* 30 | } 31 | }; 32 | Ok(implem) 33 | } 34 | 35 | fn implement_header_result( 36 | item: WithTokens, Attribute>, 37 | ) -> Result { 38 | let value = item.value; 39 | let tokens = item.tokens; 40 | let (name, value) = value.ok_or_else(|| Error::new_spanned(tokens, INVALID_HEADER))?; 41 | Ok(implement_header(name, value)) 42 | } 43 | 44 | fn implement_header(name: String, value: String) -> TokenStream { 45 | let name = format(name, "header_name"); 46 | let value = format(value, "header_value"); 47 | quote! { 48 | #name 49 | #value 50 | pretend::internal::build_header(&mut headers, header_name, header_value)?; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /pretend-reqwest/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! `reqwest` based `pretend` client 2 | //! 3 | //! Feature `blocking` enables the blocking client. 4 | 5 | #![warn(missing_docs)] 6 | #![forbid(unsafe_code)] 7 | 8 | #[cfg(feature = "blocking")] 9 | mod blocking; 10 | 11 | pub use reqwest; 12 | 13 | #[cfg(feature = "blocking")] 14 | pub use blocking::*; 15 | 16 | use pretend::client::{async_trait, Bytes, Client as PClient, Method}; 17 | use pretend::{Error, HeaderMap, Response as PResponse, Result, Url}; 18 | use reqwest::Client as RClient; 19 | use std::mem; 20 | 21 | /// `reqwest` based `pretend` client 22 | #[derive(Clone, Debug, Default)] 23 | pub struct Client { 24 | client: RClient, 25 | } 26 | 27 | impl Client { 28 | /// Constructor with custom client 29 | /// 30 | /// This constructor creates a client implementation 31 | /// for `pretend` wrapping the supplied `reqwest` client. 32 | pub fn new(client: RClient) -> Self { 33 | Client { client } 34 | } 35 | } 36 | 37 | #[async_trait] 38 | impl PClient for Client { 39 | async fn execute( 40 | &self, 41 | method: Method, 42 | url: Url, 43 | headers: HeaderMap, 44 | body: Option, 45 | ) -> Result> { 46 | let mut builder = self.client.request(method, url).headers(headers); 47 | if let Some(body) = body { 48 | builder = builder.body(body); 49 | } 50 | let response = builder.send().await; 51 | let mut response = response.map_err(Error::response)?; 52 | 53 | let status = response.status(); 54 | let headers = mem::take(response.headers_mut()); 55 | 56 | let bytes = response.bytes().await; 57 | let bytes = bytes.map_err(Error::body)?; 58 | 59 | Ok(PResponse::new(status, headers, bytes)) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/default-features-build/tests/builds/unsupported_client.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: `Rc` cannot be sent between threads safely 2 | --> $DIR/unsupported_client.rs:22:29 3 | | 4 | 22 | Err(Error::response(TestError::default())) 5 | | --------------- ^^^^^^^^^^^^^^^^^^^^ `Rc` cannot be sent between threads safely 6 | | | 7 | | required by a bound introduced by this call 8 | | 9 | = help: within `TestError`, the trait `Send` is not implemented for `Rc` 10 | note: required because it appears within the type `TestError` 11 | --> $DIR/unsupported_client.rs:8:8 12 | | 13 | 8 | struct TestError { 14 | | ^^^^^^^^^ 15 | note: required by a bound in `pretend::Error::response` 16 | --> $DIR/errors.rs:73:30 17 | | 18 | 73 | error::Error + 'static + Send + Sync 19 | | ^^^^ required by this bound in `pretend::Error::response` 20 | 21 | error[E0277]: `Rc` cannot be shared between threads safely 22 | --> $DIR/unsupported_client.rs:22:29 23 | | 24 | 22 | Err(Error::response(TestError::default())) 25 | | --------------- ^^^^^^^^^^^^^^^^^^^^ `Rc` cannot be shared between threads safely 26 | | | 27 | | required by a bound introduced by this call 28 | | 29 | = help: within `TestError`, the trait `Sync` is not implemented for `Rc` 30 | note: required because it appears within the type `TestError` 31 | --> $DIR/unsupported_client.rs:8:8 32 | | 33 | 8 | struct TestError { 34 | | ^^^^^^^^^ 35 | note: required by a bound in `pretend::Error::response` 36 | --> $DIR/errors.rs:73:37 37 | | 38 | 73 | error::Error + 'static + Send + Sync 39 | | ^^^^ required by this bound in `pretend::Error::response` 40 | -------------------------------------------------------------------------------- /pretend/src/resolver.rs: -------------------------------------------------------------------------------- 1 | //! URL resolver 2 | //! 3 | //! `pretend` supports URL resolvers. They are used to 4 | //! create a full URL from the path specified in the 5 | //! `request` attribute. 6 | //! 7 | //! By default [`UrlResolver`] is used to append the path 8 | //! to a base URL. This resolver is used in `[Pretend::with_url]`. 9 | //! 10 | //! You can implement your own resolvers to suit your needs. For 11 | //! example, you can delegate URL resolution to a load balancer. 12 | //! In this case, implement [`ResolveUrl`]. 13 | 14 | pub use url::{ParseError, Url}; 15 | 16 | /// Describe an URL resolver 17 | /// 18 | /// See module level documentation for more information. 19 | pub trait ResolveUrl { 20 | /// Resolve an URL from a path 21 | fn resolve_url(&self, path: &str) -> Result; 22 | } 23 | 24 | /// Default URL resolver 25 | /// 26 | /// This resolver appends the path to a base URL. 27 | #[derive(Clone, Debug)] 28 | pub struct UrlResolver { 29 | base: Url, 30 | } 31 | 32 | impl UrlResolver { 33 | /// Constructor 34 | pub fn new(base: Url) -> Self { 35 | UrlResolver { base } 36 | } 37 | } 38 | 39 | impl ResolveUrl for UrlResolver { 40 | fn resolve_url(&self, path: &str) -> Result { 41 | self.base.join(path) 42 | } 43 | } 44 | 45 | /// Invalid URL resolver 46 | /// 47 | /// This resolver is used when calling `[Pretend::for_client]`. It 48 | /// is will raise an error when resolving any input. 49 | /// 50 | /// `[Pretend::with_url]` or `[Pretend::with_url_resolver]` should be used to 51 | /// set a valid URL resolver. 52 | #[derive(Clone, Copy, Debug, Default)] 53 | pub struct InvalidUrlResolver; 54 | 55 | impl ResolveUrl for InvalidUrlResolver { 56 | fn resolve_url(&self, _: &str) -> Result { 57 | Err(ParseError::EmptyHost) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /pretend/tests/builds-pre-1.54/headers.stderr: -------------------------------------------------------------------------------- 1 | error: Failed to generate pretend implementation 2 | --> $DIR/headers.rs:5:1 3 | | 4 | 5 | #[pretend] 5 | | ^^^^^^^^^^ 6 | | 7 | = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) 8 | 9 | error: Failed to generate method implementation 10 | --> $DIR/headers.rs:7:5 11 | | 12 | 7 | / #[request(method = "GET", path = "/get")] 13 | 8 | | #[header(value = "test")] 14 | 9 | | async fn test_1(&self) -> Result<()>; 15 | | |_________________________________________^ 16 | 17 | error: `#[header]` attribute must only have `name` and `value` 18 | --> $DIR/headers.rs:8:5 19 | | 20 | 8 | #[header(value = "test")] 21 | | ^^^^^^^^^^^^^^^^^^^^^^^^^ 22 | 23 | error: Failed to generate method implementation 24 | --> $DIR/headers.rs:10:5 25 | | 26 | 10 | / #[request(method = "GET", path = "/get")] 27 | 11 | | #[header(name = "X-Test")] 28 | 12 | | async fn test_2(&self) -> Result<()>; 29 | | |_________________________________________^ 30 | 31 | error: `#[header]` attribute must only have `name` and `value` 32 | --> $DIR/headers.rs:11:5 33 | | 34 | 11 | #[header(name = "X-Test")] 35 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 36 | 37 | error: Failed to generate method implementation 38 | --> $DIR/headers.rs:13:5 39 | | 40 | 13 | / #[request(method = "GET", path = "/get")] 41 | 14 | | #[header(name = "X-Test", value = "test", other = "something")] 42 | 15 | | async fn test_3(&self) -> Result<()>; 43 | | |_________________________________________^ 44 | 45 | error: `#[header]` attribute must only have `name` and `value` 46 | --> $DIR/headers.rs:14:5 47 | | 48 | 14 | #[header(name = "X-Test", value = "test", other = "something")] 49 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 50 | -------------------------------------------------------------------------------- /pretend/tests/builds/headers.stderr: -------------------------------------------------------------------------------- 1 | error: Failed to generate pretend implementation 2 | --> $DIR/headers.rs:5:1 3 | | 4 | 5 | #[pretend] 5 | | ^^^^^^^^^^ 6 | | 7 | = note: this error originates in the attribute macro `pretend` (in Nightly builds, run with -Z macro-backtrace for more info) 8 | 9 | error: Failed to generate method implementation 10 | --> $DIR/headers.rs:7:5 11 | | 12 | 7 | / #[request(method = "GET", path = "/get")] 13 | 8 | | #[header(value = "test")] 14 | 9 | | async fn test_1(&self) -> Result<()>; 15 | | |_________________________________________^ 16 | 17 | error: `#[header]` attribute must only have `name` and `value` 18 | --> $DIR/headers.rs:8:5 19 | | 20 | 8 | #[header(value = "test")] 21 | | ^^^^^^^^^^^^^^^^^^^^^^^^^ 22 | 23 | error: Failed to generate method implementation 24 | --> $DIR/headers.rs:10:5 25 | | 26 | 10 | / #[request(method = "GET", path = "/get")] 27 | 11 | | #[header(name = "X-Test")] 28 | 12 | | async fn test_2(&self) -> Result<()>; 29 | | |_________________________________________^ 30 | 31 | error: `#[header]` attribute must only have `name` and `value` 32 | --> $DIR/headers.rs:11:5 33 | | 34 | 11 | #[header(name = "X-Test")] 35 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 36 | 37 | error: Failed to generate method implementation 38 | --> $DIR/headers.rs:13:5 39 | | 40 | 13 | / #[request(method = "GET", path = "/get")] 41 | 14 | | #[header(name = "X-Test", value = "test", other = "something")] 42 | 15 | | async fn test_3(&self) -> Result<()>; 43 | | |_________________________________________^ 44 | 45 | error: `#[header]` attribute must only have `name` and `value` 46 | --> $DIR/headers.rs:14:5 47 | | 48 | 14 | #[header(name = "X-Test", value = "test", other = "something")] 49 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 50 | -------------------------------------------------------------------------------- /pretend/tests/builds-pre-1.54/requests.stderr: -------------------------------------------------------------------------------- 1 | error: Failed to generate pretend implementation 2 | --> $DIR/requests.rs:5:1 3 | | 4 | 5 | #[pretend] 5 | | ^^^^^^^^^^ 6 | | 7 | = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) 8 | 9 | error: Method must have the `#[request]` attribute 10 | --> $DIR/requests.rs:7:5 11 | | 12 | 7 | async fn test_1(&self) -> Result<()>; 13 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 14 | 15 | error: Method must have the `#[request]` attribute only once 16 | --> $DIR/requests.rs:8:5 17 | | 18 | 8 | / #[request(method = "GET", path = "/get")] 19 | 9 | | #[request(method = "GET", path = "/get")] 20 | 10 | | async fn test_2(&self) -> Result<()>; 21 | | |_________________________________________^ 22 | 23 | error: `#[request]` attribute defined here 24 | --> $DIR/requests.rs:8:5 25 | | 26 | 8 | #[request(method = "GET", path = "/get")] 27 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 28 | 29 | error: `#[request]` attribute defined here 30 | --> $DIR/requests.rs:9:5 31 | | 32 | 9 | #[request(method = "GET", path = "/get")] 33 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 34 | 35 | error: `#[request]` attribute must only have `method` and `path` 36 | --> $DIR/requests.rs:11:5 37 | | 38 | 11 | #[request(method = "GET")] 39 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 40 | 41 | error: `#[request]` attribute must only have `method` and `path` 42 | --> $DIR/requests.rs:13:5 43 | | 44 | 13 | #[request(path = "/get")] 45 | | ^^^^^^^^^^^^^^^^^^^^^^^^^ 46 | 47 | error: `#[request]` attribute must only have `method` and `path` 48 | --> $DIR/requests.rs:15:5 49 | | 50 | 15 | #[request(method = "GET", path = "/get", other = "something")] 51 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 52 | -------------------------------------------------------------------------------- /pretend/tests/builds/requests.stderr: -------------------------------------------------------------------------------- 1 | error: Failed to generate pretend implementation 2 | --> $DIR/requests.rs:5:1 3 | | 4 | 5 | #[pretend] 5 | | ^^^^^^^^^^ 6 | | 7 | = note: this error originates in the attribute macro `pretend` (in Nightly builds, run with -Z macro-backtrace for more info) 8 | 9 | error: Method must have the `#[request]` attribute 10 | --> $DIR/requests.rs:7:5 11 | | 12 | 7 | async fn test_1(&self) -> Result<()>; 13 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 14 | 15 | error: Method must have the `#[request]` attribute only once 16 | --> $DIR/requests.rs:8:5 17 | | 18 | 8 | / #[request(method = "GET", path = "/get")] 19 | 9 | | #[request(method = "GET", path = "/get")] 20 | 10 | | async fn test_2(&self) -> Result<()>; 21 | | |_________________________________________^ 22 | 23 | error: `#[request]` attribute defined here 24 | --> $DIR/requests.rs:8:5 25 | | 26 | 8 | #[request(method = "GET", path = "/get")] 27 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 28 | 29 | error: `#[request]` attribute defined here 30 | --> $DIR/requests.rs:9:5 31 | | 32 | 9 | #[request(method = "GET", path = "/get")] 33 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 34 | 35 | error: `#[request]` attribute must only have `method` and `path` 36 | --> $DIR/requests.rs:11:5 37 | | 38 | 11 | #[request(method = "GET")] 39 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 40 | 41 | error: `#[request]` attribute must only have `method` and `path` 42 | --> $DIR/requests.rs:13:5 43 | | 44 | 13 | #[request(path = "/get")] 45 | | ^^^^^^^^^^^^^^^^^^^^^^^^^ 46 | 47 | error: `#[request]` attribute must only have `method` and `path` 48 | --> $DIR/requests.rs:15:5 49 | | 50 | 15 | #[request(method = "GET", path = "/get", other = "something")] 51 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 52 | -------------------------------------------------------------------------------- /pretend-awc/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! `awc` based `pretend` client 2 | 3 | #![warn(missing_docs)] 4 | #![forbid(unsafe_code)] 5 | 6 | pub use awc; 7 | 8 | use awc::http::{HeaderName, HeaderValue}; 9 | use awc::Client as AClient; 10 | use pretend::client::{async_trait, Bytes, LocalClient, Method}; 11 | use pretend::http::header::{HeaderName as PHeaderName, HeaderValue as PHeaderValue}; 12 | use pretend::{Error, HeaderMap, Response, Result, Url}; 13 | 14 | /// `awc` based `pretend` client 15 | #[derive(Clone, Default)] 16 | pub struct Client { 17 | client: AClient, 18 | } 19 | 20 | impl Client { 21 | /// Constructor with custom client 22 | /// 23 | /// This constructor creates a client implementation 24 | /// for `pretend` wrapping the supplied `awc` client. 25 | pub fn new(client: AClient) -> Self { 26 | Client { client } 27 | } 28 | } 29 | 30 | #[async_trait(?Send)] 31 | impl LocalClient for Client { 32 | async fn execute( 33 | &self, 34 | method: Method, 35 | url: Url, 36 | headers: HeaderMap, 37 | body: Option, 38 | ) -> Result> { 39 | let mut request = self.client.request(method, url.as_str()); 40 | for (name, value) in headers.iter() { 41 | request = request.set_header(name, value.as_bytes()); 42 | } 43 | 44 | let future = if let Some(body) = body { 45 | request.send_body(body.to_vec()) 46 | } else { 47 | request.send() 48 | }; 49 | 50 | let mut response = future.await.map_err(Error::response)?; 51 | let status = response.status(); 52 | let headers = response.headers(); 53 | let headers = headers.iter().map(create_header).collect::(); 54 | let future = response.body(); 55 | let result = future.await.map_err(Error::body)?; 56 | Ok(Response::new(status, headers, Bytes::from(result.to_vec()))) 57 | } 58 | } 59 | 60 | fn create_header((name, value): (&HeaderName, &HeaderValue)) -> (PHeaderName, PHeaderValue) { 61 | (PHeaderName::from(name), PHeaderValue::from(value)) 62 | } 63 | -------------------------------------------------------------------------------- /pretend/src/interceptor.rs: -------------------------------------------------------------------------------- 1 | //! Request interceptors 2 | //! 3 | //! `pretend` support request interceptors. They are used 4 | //! to post-process an automatically generated request before 5 | //! it is being executed. 6 | //! 7 | //! Interceptors can facilitate setting common headers, like 8 | //! an user-agent, or injecting an authentication token. 9 | //! 10 | //! By default a [`NoopRequestInterceptor`] is used, and will 11 | //! not modify the request. 12 | //! 13 | //! Custom interceptors are defined by implementing [`InterceptRequest`]. 14 | //! 15 | //! # Error handling 16 | //! 17 | //! Request interceptors are allowed to fail. They return a pretend 18 | //! `Result` that will be returned as is to the caller. Prefer using 19 | //! `Error::Request` when reporting an error. 20 | 21 | pub use crate::client::Method; 22 | pub use crate::{HeaderMap, Result, Url}; 23 | 24 | /// An HTTP request 25 | #[non_exhaustive] 26 | pub struct Request { 27 | /// Request method 28 | pub method: Method, 29 | /// Full request URL 30 | pub url: Url, 31 | /// Request headers 32 | pub headers: HeaderMap, 33 | } 34 | 35 | impl Request { 36 | /// Constructor 37 | /// 38 | /// This constructor can be used 39 | /// to construct customized requests 40 | /// inside a request interceptor. 41 | pub fn new(method: Method, url: Url, headers: HeaderMap) -> Self { 42 | Request { 43 | method, 44 | url, 45 | headers, 46 | } 47 | } 48 | } 49 | 50 | /// Describe a request interceptor 51 | /// 52 | /// See module level documentation for more information. 53 | pub trait InterceptRequest { 54 | /// Intercept a request, returning a customized request 55 | fn intercept(&self, request: Request) -> Result; 56 | } 57 | 58 | /// Default request interceptor 59 | /// 60 | /// This request interceptor will not modify 61 | /// the request. 62 | #[derive(Clone, Copy, Debug, Default)] 63 | pub struct NoopRequestInterceptor; 64 | 65 | impl InterceptRequest for NoopRequestInterceptor { 66 | fn intercept(&self, request: Request) -> Result { 67 | Ok(request) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /pretend-codegen/src/method/body.rs: -------------------------------------------------------------------------------- 1 | use super::BodyKind; 2 | use crate::errors::{ErrorsExt, TOO_MANY_BODIES, TOO_MANY_BODIES_HINT}; 3 | use crate::utils::{parse_param_name, Single, WithTokens}; 4 | use proc_macro2::{Ident, TokenStream}; 5 | use quote::quote; 6 | use syn::{Error, Result, TraitItemMethod}; 7 | 8 | pub(crate) fn implement_body(method: &TraitItemMethod) -> Result { 9 | let kind = get_body(method)?; 10 | let implem = match kind { 11 | BodyKind::None => quote! { 12 | let body = pretend::internal::Body::<()>::None; 13 | }, 14 | BodyKind::Body => quote! { 15 | let body = pretend::internal::Body::<()>::Raw(pretend::client::Bytes::from(body)); 16 | }, 17 | BodyKind::Form => quote! { 18 | let body = pretend::internal::Body::Form(&form); 19 | }, 20 | BodyKind::Json => quote! { 21 | let body = pretend::internal::Body::Json(&json); 22 | }, 23 | }; 24 | Ok(implem) 25 | } 26 | 27 | fn get_body(method: &TraitItemMethod) -> Result { 28 | let inputs = &method.sig.inputs; 29 | let single = inputs 30 | .iter() 31 | .filter_map(parse_param_name) 32 | .filter_map(parse_body_kind) 33 | .collect::>(); 34 | 35 | match single { 36 | Single::None => Ok(BodyKind::None), 37 | Single::Single(item) => Ok(item.value), 38 | Single::TooMany(bodies) => { 39 | let errors = bodies 40 | .into_iter() 41 | .map(|item| Error::new_spanned(item.tokens, TOO_MANY_BODIES_HINT)) 42 | .collect::>(); 43 | 44 | errors.into_result(|| Error::new_spanned(&method.sig, TOO_MANY_BODIES)) 45 | } 46 | } 47 | } 48 | 49 | fn parse_body_kind(ident: &Ident) -> Option> { 50 | if ident == "body" { 51 | Some(WithTokens::new(BodyKind::Body, ident)) 52 | } else if ident == "form" { 53 | Some(WithTokens::new(BodyKind::Form, ident)) 54 | } else if ident == "json" { 55 | Some(WithTokens::new(BodyKind::Json, ident)) 56 | } else { 57 | None 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /pretend-codegen/src/utils/attr.rs: -------------------------------------------------------------------------------- 1 | use super::WithTokens; 2 | use syn::{Attribute, Lit, Meta, MetaList, MetaNameValue, NestedMeta}; 3 | 4 | pub(crate) fn parse_name_value_2_attr<'a>( 5 | attr: &'a Attribute, 6 | name: &'static str, 7 | key_name: &'static str, 8 | value_name: &'static str, 9 | ) -> Option, Attribute>> { 10 | let name_values = parse_name_value_attr(attr, name)?; 11 | 12 | let mut key_result = None; 13 | let mut value_result = None; 14 | let mut valid = true; 15 | 16 | for (name, value) in name_values { 17 | if name == key_name { 18 | key_result = Some(value); 19 | } else if name == value_name { 20 | value_result = Some(value); 21 | } else { 22 | valid = false; 23 | } 24 | } 25 | 26 | if let (Some(key), Some(value), true) = (key_result, value_result, valid) { 27 | Some(WithTokens::new(Some((key, value)), attr)) 28 | } else { 29 | Some(WithTokens::new(None, attr)) 30 | } 31 | } 32 | 33 | // Returns a list of name-value 34 | fn parse_name_value_attr(attr: &Attribute, name: &str) -> Option> { 35 | let list = get_meta_list(attr)?; 36 | let path = list.path.get_ident()?; 37 | if path == name { 38 | let nested = list.nested; 39 | let attr = nested.iter().filter_map(parse_nested_meta).collect(); 40 | Some(attr) 41 | } else { 42 | None 43 | } 44 | } 45 | 46 | fn get_meta_list(attr: &Attribute) -> Option { 47 | let meta = attr.parse_meta().ok()?; 48 | match meta { 49 | Meta::List(list) => Some(list), 50 | _ => None, 51 | } 52 | } 53 | 54 | fn parse_nested_meta(meta: &NestedMeta) -> Option<(String, String)> { 55 | match meta { 56 | NestedMeta::Meta(Meta::NameValue(name_value)) => parse_meta_name_value(name_value), 57 | _ => None, 58 | } 59 | } 60 | 61 | fn parse_meta_name_value(name_value: &MetaNameValue) -> Option<(String, String)> { 62 | let path = name_value.path.get_ident(); 63 | match (path, &name_value.lit) { 64 | (Some(path), Lit::Str(value)) => Some((path.to_string(), value.value())), 65 | _ => None, 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /pretend/tests/test_construction.rs: -------------------------------------------------------------------------------- 1 | mod runtimes; 2 | mod server; 3 | 4 | use pretend::interceptor::NoopRequestInterceptor; 5 | use pretend::resolver::UrlResolver; 6 | use pretend::{pretend, Pretend, Result, Url}; 7 | use pretend_reqwest::Client; 8 | 9 | #[pretend] 10 | trait TestApi { 11 | #[request(method = "GET", path = "/method")] 12 | async fn get(&self) -> Result; 13 | } 14 | 15 | #[tokio::test] 16 | async fn pretend_with_only_client_cannot_be_used() { 17 | let client = Pretend::for_client(Client::default()); 18 | let result = client.get().await; 19 | assert!(result.is_err()); 20 | } 21 | 22 | fn pretend_construct_with_client_and_resolver() { 23 | runtimes::block_on(async { 24 | let url = Url::parse(server::URL).unwrap(); 25 | let client = Client::default(); 26 | let resolver = UrlResolver::new(url); 27 | let client = Pretend::for_client(client).with_url_resolver(resolver); 28 | let result = client.get().await; 29 | assert!(result.is_ok()); 30 | }) 31 | } 32 | 33 | fn pretend_construct_with_client_resolver_and_interceptor() { 34 | runtimes::block_on(async { 35 | let url = Url::parse(server::URL).unwrap(); 36 | let client = Client::default(); 37 | let resolver = UrlResolver::new(url); 38 | let interceptor = NoopRequestInterceptor; 39 | let client = Pretend::for_client(client) 40 | .with_url_resolver(resolver) 41 | .with_request_interceptor(interceptor); 42 | let result = client.get().await; 43 | assert!(result.is_ok()); 44 | }) 45 | } 46 | 47 | fn pretend_construct_with_constructor() { 48 | runtimes::block_on(async { 49 | let url = Url::parse(server::URL).unwrap(); 50 | let client = Client::default(); 51 | let resolver = UrlResolver::new(url); 52 | let interceptor = NoopRequestInterceptor; 53 | let client = Pretend::new(client, resolver, interceptor); 54 | let result = client.get().await; 55 | assert!(result.is_ok()); 56 | }) 57 | } 58 | 59 | #[test] 60 | fn pretend_constructors() { 61 | server::test(|| { 62 | pretend_construct_with_client_and_resolver(); 63 | pretend_construct_with_client_resolver_and_interceptor(); 64 | pretend_construct_with_constructor(); 65 | }) 66 | } 67 | -------------------------------------------------------------------------------- /pretend-isahc/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! `isahc` based `pretend` client 2 | 3 | #![warn(missing_docs)] 4 | #![forbid(unsafe_code)] 5 | 6 | pub use isahc; 7 | 8 | use isahc::http::Request; 9 | use isahc::{AsyncBody, AsyncReadResponseExt, HttpClient}; 10 | use pretend::client::{async_trait, Bytes, Client as PClient, Method}; 11 | use pretend::{Error, HeaderMap, Response, Result, Url}; 12 | use std::mem; 13 | 14 | /// `ishac` based `pretend` client 15 | #[derive(Clone, Debug)] 16 | pub struct Client { 17 | client: HttpClient, 18 | } 19 | 20 | impl Client { 21 | /// Constructor with custom client 22 | /// 23 | /// This constructor creates a client implementation 24 | /// for `pretend` wrapping the supplied `isahc` client. 25 | pub fn with_client(client: HttpClient) -> Self { 26 | Client { client } 27 | } 28 | 29 | /// Constructor 30 | /// 31 | /// This constructor creates a client implementation 32 | /// for `pretend` using a default `isahc` client. 33 | pub fn new() -> Result { 34 | let client = HttpClient::new().map_err(Error::client)?; 35 | Ok(Client { client }) 36 | } 37 | } 38 | 39 | #[async_trait] 40 | impl PClient for Client { 41 | async fn execute( 42 | &self, 43 | method: Method, 44 | url: Url, 45 | headers: HeaderMap, 46 | body: Option, 47 | ) -> Result> { 48 | let mut builder = Request::builder().method(method).uri(url.as_str()); 49 | 50 | for (name, value) in headers.iter() { 51 | builder = builder.header(name, value); 52 | } 53 | 54 | let request = if let Some(body) = body { 55 | builder.body(AsyncBody::from_bytes_static(body)) 56 | } else { 57 | builder.body(AsyncBody::empty()) 58 | }; 59 | 60 | let request = request.map_err(Error::request)?; 61 | let response = self.client.send_async(request).await; 62 | let mut response = response.map_err(Error::response)?; 63 | 64 | let status = mem::take(response.status_mut()); 65 | let headers = mem::take(response.headers_mut()); 66 | let mut body = Vec::new(); 67 | let result = response.copy_to(&mut body).await; 68 | result.map_err(Error::body)?; 69 | Ok(Response::new(status, headers, Bytes::from(body))) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /pretend-ureq/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! `ureq` based `pretend` client 2 | 3 | #![warn(missing_docs)] 4 | #![forbid(unsafe_code)] 5 | 6 | pub use ureq; 7 | 8 | use pretend::client::{BlockingClient, Bytes, Method}; 9 | use pretend::http::header::HeaderName; 10 | use pretend::http::HeaderValue; 11 | use pretend::{Error, HeaderMap, Response as PResponse, Result, StatusCode, Url}; 12 | use std::convert::TryFrom; 13 | use std::io::Read; 14 | use ureq::Agent; 15 | 16 | /// `ureq` based `pretend` client 17 | pub struct Client { 18 | agent: Agent, 19 | } 20 | 21 | impl Client { 22 | /// Constructor with custom agent 23 | /// 24 | /// This constructor creates a client implementation 25 | /// for `pretend` wrapping the supplied `ureq` agent. 26 | pub fn new(agent: Agent) -> Self { 27 | Client { agent } 28 | } 29 | } 30 | 31 | impl BlockingClient for Client { 32 | fn execute( 33 | &self, 34 | method: Method, 35 | url: Url, 36 | headers: HeaderMap, 37 | body: Option, 38 | ) -> Result> { 39 | let mut request = self.agent.request_url(method.as_str(), &url); 40 | 41 | for (name, value) in headers.iter() { 42 | let value = value.to_str(); 43 | let value = value.map_err(|err| Error::Request(Box::new(err)))?; 44 | request = request.set(name.as_str(), value); 45 | } 46 | 47 | let response = if let Some(body) = body { 48 | request.send_bytes(&body) 49 | } else { 50 | request.call() 51 | }; 52 | 53 | let response = response.map_err(|err| Error::Response(Box::new(err)))?; 54 | 55 | let status = StatusCode::from_u16(response.status()); 56 | let status = status.map_err(|err| Error::Response(Box::new(err)))?; 57 | 58 | let mut headers = HeaderMap::new(); 59 | for name in response.headers_names() { 60 | let values = response.all(&name); 61 | 62 | let name = HeaderName::try_from(&name); 63 | let name = name.map_err(|err| Error::Response(Box::new(err)))?; 64 | 65 | for value in values { 66 | let value = HeaderValue::try_from(value); 67 | let value = value.map_err(|err| Error::Response(Box::new(err)))?; 68 | 69 | headers.append(&name, value); 70 | } 71 | } 72 | 73 | let mut body = Vec::new(); 74 | let mut reader = response.into_reader(); 75 | reader 76 | .read_to_end(&mut body) 77 | .map_err(|err| Error::Response(Box::new(err)))?; 78 | 79 | Ok(PResponse::new(status, headers, Bytes::from(body))) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /pretend/src/errors.rs: -------------------------------------------------------------------------------- 1 | use crate::StatusCode; 2 | use std::{error, result}; 3 | use thiserror::Error; 4 | 5 | macro_rules! impl_error { 6 | ($ty:ty, $($b:tt)*) => { 7 | /// Pretend errors 8 | /// 9 | /// This error type wraps errors emitted 10 | /// by `pretend` or by client implementations. 11 | #[derive(Error, Debug)] 12 | pub enum Error { 13 | /// Error when creating a client implementation 14 | #[error("Failed to create client")] 15 | Client(#[source] $ty), 16 | /// Error when building the request 17 | #[error("Invalid request")] 18 | Request(#[source] $ty), 19 | /// Error when executing the request 20 | #[error("Failed to execute request")] 21 | Response(#[source] $ty), 22 | /// Error when parsing the response body 23 | #[error("Failed to read response body")] 24 | Body(#[source] $ty), 25 | /// HTTP status error 26 | /// 27 | /// This error is returned when the request failed with 28 | /// an HTTP error status. It is only returned when methods 29 | /// returns bodies. 30 | #[error("HTTP {0}")] 31 | Status(StatusCode), 32 | } 33 | 34 | impl Error { 35 | /// Construct a new `Client` error 36 | pub fn client(err: E) -> Self 37 | where 38 | E: $($b)*, 39 | { 40 | Error::Client(Box::new(err)) 41 | } 42 | 43 | /// Construct a new `Request` error 44 | pub fn request(err: E) -> Self 45 | where 46 | E: $($b)*, 47 | { 48 | Error::Request(Box::new(err)) 49 | } 50 | 51 | /// Construct a new `Response` error 52 | pub fn response(err: E) -> Self 53 | where 54 | E: $($b)*, 55 | { 56 | Error::Response(Box::new(err)) 57 | } 58 | 59 | /// Construct a new `Body` error 60 | pub fn body(err: E) -> Self 61 | where 62 | E: $($b)*, 63 | { 64 | Error::Body(Box::new(err)) 65 | } 66 | } 67 | }; 68 | } 69 | 70 | #[cfg(not(feature = "local-error"))] 71 | impl_error!( 72 | Box, 73 | error::Error + 'static + Send + Sync 74 | ); 75 | #[cfg(feature = "local-error")] 76 | impl_error!(Box, error::Error + 'static); 77 | 78 | /// Pretend Result type 79 | pub type Result = result::Result; 80 | -------------------------------------------------------------------------------- /pretend/src/client.rs: -------------------------------------------------------------------------------- 1 | //! Client traits 2 | //! 3 | //! This module contains traits to be implemented by a `pretend` client. `pretend` clients 4 | //! implementations are responsible of executing the actual HTTP requests. Implementations 5 | //! delegates to other crates. 6 | //! 7 | //! `pretend` support 3 kind of clients: 8 | //! 9 | //! - [`Client`] for asynchronous clients 10 | //! - [`LocalClient`] for asynchronous local clients (clients that are not `Send` + `Sync`) 11 | //! - [`BlockingClient`] for blocking clients 12 | //! 13 | //! These traits wrap the underlying HTTP client and are responsible of executing the request 14 | //! via `execute`. This method takes a method, url, header and body (as raw bytes) and should 15 | //! return a response with raw bytes as body. 16 | //! 17 | //! Since this crate uses `async_trait` to support futures in trait, `Client` 18 | //! implementations should be marked with `#[client::async_trait]` and 19 | //! `LocalClient` should use `#[client::async_trait(?Send)]`. 20 | 21 | pub use async_trait::async_trait; 22 | pub use bytes::Bytes; 23 | pub use http::Method; 24 | 25 | use crate::{HeaderMap, Response, Result, Url}; 26 | 27 | /// `pretend` client 28 | /// 29 | /// See module level documentation for more information. 30 | #[async_trait] 31 | pub trait Client { 32 | /// Execute a request 33 | async fn execute( 34 | &self, 35 | method: Method, 36 | url: Url, 37 | headers: HeaderMap, 38 | body: Option, 39 | ) -> Result>; 40 | } 41 | 42 | /// `pretend` local client 43 | /// 44 | /// See module level documentation for more information. 45 | #[async_trait(?Send)] 46 | pub trait LocalClient { 47 | /// Execute a request 48 | async fn execute( 49 | &self, 50 | method: Method, 51 | url: Url, 52 | headers: HeaderMap, 53 | body: Option, 54 | ) -> Result>; 55 | } 56 | 57 | /// `pretend` blocking client 58 | /// 59 | /// See module level documentation for more information. 60 | pub trait BlockingClient { 61 | /// Execute a request 62 | fn execute( 63 | &self, 64 | method: Method, 65 | url: Url, 66 | headers: HeaderMap, 67 | body: Option, 68 | ) -> Result>; 69 | } 70 | 71 | /// `pretend` local client 72 | /// 73 | /// See module level documentation for more information. 74 | #[async_trait(?Send)] 75 | impl LocalClient for C 76 | where 77 | C: Client, 78 | { 79 | async fn execute( 80 | &self, 81 | method: Method, 82 | url: Url, 83 | headers: HeaderMap, 84 | body: Option, 85 | ) -> Result> { 86 | Client::execute(self, method, url, headers, body).await 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /pretend/tests/test_clients.rs: -------------------------------------------------------------------------------- 1 | mod clients_tester; 2 | mod runtimes; 3 | mod server; 4 | 5 | use clients_tester::{ 6 | ClientsTester, TestableClient, TokioTestableClient, TokioTestableLocalClient, 7 | }; 8 | use pretend::client::{Bytes, Client, LocalClient, Method}; 9 | use pretend::{HeaderMap, Response, Result, Url}; 10 | use pretend_awc::Client as AClient; 11 | use pretend_isahc::Client as IClient; 12 | use pretend_reqwest::{BlockingClient as RBlockingClient, Client as RClient}; 13 | use pretend_ureq::ureq::AgentBuilder; 14 | use pretend_ureq::Client as UClient; 15 | 16 | fn create_testable(client: C) -> Box 17 | where 18 | C: Client + 'static, 19 | { 20 | Box::new(TokioTestableClient::new(client, runtimes::create_runtime())) 21 | } 22 | 23 | fn create_testable_local(client: C) -> Box 24 | where 25 | C: LocalClient + 'static, 26 | { 27 | Box::new(TokioTestableLocalClient::new( 28 | client, 29 | runtimes::create_runtime(), 30 | )) 31 | } 32 | 33 | struct TestableAwcClient; 34 | 35 | #[actix_web::main] 36 | async fn awc_execute( 37 | method: Method, 38 | url: Url, 39 | headers: HeaderMap, 40 | body: Option, 41 | ) -> Result> { 42 | AClient::default().execute(method, url, headers, body).await 43 | } 44 | 45 | impl TestableClient for TestableAwcClient { 46 | fn execute( 47 | &self, 48 | method: Method, 49 | url: Url, 50 | headers: HeaderMap, 51 | body: Option, 52 | ) -> Result> { 53 | awc_execute(method, url, headers, body) 54 | } 55 | } 56 | 57 | #[test] 58 | fn test_all_clients() { 59 | server::test(|| { 60 | let url = Url::parse(server::URL).unwrap(); 61 | test_clients(url.clone()); 62 | test_local_clients(url.clone()); 63 | test_blocking_clients(url.clone()); 64 | }); 65 | } 66 | 67 | fn test_clients(url: Url) { 68 | let clients = vec![ 69 | create_testable(RClient::default()), 70 | create_testable(IClient::new().unwrap()), 71 | ]; 72 | let tester = ClientsTester::new(url, clients); 73 | tester.test(); 74 | } 75 | 76 | fn test_local_clients(url: Url) { 77 | let clients: Vec> = vec![ 78 | create_testable_local(RClient::default()), 79 | create_testable_local(IClient::new().unwrap()), 80 | Box::new(TestableAwcClient), 81 | ]; 82 | let tester = ClientsTester::new(url, clients); 83 | tester.test(); 84 | } 85 | 86 | fn test_blocking_clients(url: Url) { 87 | let clients: Vec> = vec![ 88 | Box::new(RBlockingClient::default()), 89 | Box::new(UClient::new(AgentBuilder::new().build())), 90 | ]; 91 | let tester = ClientsTester::new(url, clients); 92 | tester.test(); 93 | } 94 | -------------------------------------------------------------------------------- /pretend/examples/bodies.rs: -------------------------------------------------------------------------------- 1 | use pretend::{pretend, Pretend, Result, Url}; 2 | use pretend_reqwest::Client; 3 | use serde::Serialize; 4 | 5 | // This example show how to send various body types to https://httpbin.org 6 | 7 | #[derive(Clone, Serialize)] 8 | struct Data { 9 | first: String, 10 | second: i32, 11 | } 12 | 13 | #[pretend] 14 | trait HttpBin { 15 | #[request(method = "POST", path = "/anything")] 16 | #[header(name = "Content-Type", value = "text/plain")] 17 | async fn post_string_ref(&self, body: &'static str) -> Result; 18 | 19 | #[request(method = "POST", path = "/anything")] 20 | #[header(name = "Content-Type", value = "text/plain")] 21 | async fn post_string(&self, body: String) -> Result; 22 | 23 | #[request(method = "POST", path = "/anything")] 24 | #[header(name = "Content-Type", value = "application/octet-stream")] 25 | async fn post_bytes_ref(&self, body: &'static [u8]) -> Result; 26 | 27 | #[request(method = "POST", path = "/anything")] 28 | #[header(name = "Content-Type", value = "application/octet-stream")] 29 | async fn post_bytes(&self, body: Vec) -> Result; 30 | 31 | #[request(method = "POST", path = "/anything")] 32 | async fn post_form_ref(&self, form: &Data) -> Result; 33 | 34 | #[request(method = "POST", path = "/anything")] 35 | async fn post_form(&self, form: Data) -> Result; 36 | 37 | #[request(method = "POST", path = "/anything")] 38 | async fn post_json_ref(&self, json: &Data) -> Result; 39 | 40 | #[request(method = "POST", path = "/anything")] 41 | async fn post_json(&self, json: Data) -> Result; 42 | } 43 | 44 | fn create_pretend() -> impl HttpBin { 45 | let url = Url::parse("https://httpbin.org").unwrap(); 46 | Pretend::for_client(Client::default()).with_url(url) 47 | } 48 | 49 | #[tokio::main] 50 | async fn main() { 51 | let pretend = create_pretend(); 52 | 53 | let result = pretend.post_string_ref("Hello").await.unwrap(); 54 | println!("{}", result); 55 | 56 | let result = pretend.post_string("Hello".to_string()).await.unwrap(); 57 | println!("{}", result); 58 | 59 | let result = pretend.post_bytes_ref(&[1, 2, 3]).await.unwrap(); 60 | println!("{}", result); 61 | 62 | let result = pretend.post_bytes(vec![1, 2, 3]).await.unwrap(); 63 | println!("{}", result); 64 | 65 | let data = Data { 66 | first: "Hello".to_string(), 67 | second: 123, 68 | }; 69 | 70 | let result = pretend.post_form_ref(&data).await.unwrap(); 71 | println!("{}", result); 72 | 73 | let result = pretend.post_form(data.clone()).await.unwrap(); 74 | println!("{}", result); 75 | 76 | let result = pretend.post_json_ref(&data).await.unwrap(); 77 | println!("{}", result); 78 | 79 | let result = pretend.post_json(data.clone()).await.unwrap(); 80 | println!("{}", result); 81 | } 82 | -------------------------------------------------------------------------------- /pretend-codegen/src/method.rs: -------------------------------------------------------------------------------- 1 | mod attr; 2 | mod body; 3 | mod checks; 4 | mod headers; 5 | mod query; 6 | mod request; 7 | 8 | use self::body::implement_body; 9 | use self::checks::{check_correct_receiver, check_no_generics}; 10 | use self::headers::implement_headers; 11 | use self::query::implement_query; 12 | use self::request::get_request; 13 | use crate::errors::UNSUPPORTED_TRAIT_ITEM; 14 | use crate::format::format; 15 | use crate::ClientKind; 16 | use proc_macro2::{Ident, Span, TokenStream}; 17 | use quote::quote; 18 | use std::mem; 19 | use syn::{Attribute, Error, Result, TraitItem, TraitItemMethod}; 20 | 21 | pub(crate) use self::attr::{parse_header_attr, parse_request_attr}; 22 | 23 | pub(crate) enum BodyKind { 24 | None, 25 | Body, 26 | Form, 27 | Json, 28 | } 29 | 30 | pub(crate) fn trait_item(item: &TraitItem) -> TraitItem { 31 | match item { 32 | TraitItem::Method(item) => { 33 | let mut item = item.clone(); 34 | let attrs = mem::take(&mut item.attrs); 35 | item.attrs = attrs 36 | .into_iter() 37 | .filter(|attr| !is_attribute(attr)) 38 | .collect(); 39 | TraitItem::Method(item) 40 | } 41 | _ => item.clone(), 42 | } 43 | } 44 | 45 | pub(crate) fn trait_item_implem(item: &TraitItem, kind: &ClientKind) -> Result { 46 | match item { 47 | TraitItem::Method(method) => implement_method(method, kind), 48 | _ => Err(Error::new_spanned(item, UNSUPPORTED_TRAIT_ITEM)), 49 | } 50 | } 51 | 52 | fn is_attribute(attr: &Attribute) -> bool { 53 | let is_request = parse_request_attr(attr).is_some(); 54 | let is_header = parse_header_attr(attr).is_some(); 55 | is_request || is_header 56 | } 57 | 58 | fn implement_method(method: &TraitItemMethod, kind: &ClientKind) -> Result { 59 | check_no_generics(method)?; 60 | check_correct_receiver(method)?; 61 | 62 | let query = implement_query(method); 63 | let body = implement_body(method)?; 64 | let headers = implement_headers(method)?; 65 | 66 | let sig = &method.sig; 67 | let (method, path) = get_request(method)?; 68 | let method = Ident::new(&method, Span::call_site()); 69 | let path = format(path, "path"); 70 | 71 | let execute_request = match kind { 72 | ClientKind::Async => quote! { 73 | support.request(method, url, headers, body).await 74 | }, 75 | ClientKind::AsyncLocal => quote! { 76 | support.request_local(method, url, headers, body).await 77 | }, 78 | ClientKind::Blocking => quote! { 79 | support.request_blocking(method, url, headers, body) 80 | }, 81 | }; 82 | 83 | Ok(quote! { 84 | #sig { 85 | let method = pretend::client::Method::#method; 86 | #path 87 | #headers 88 | #body 89 | 90 | let support = pretend::internal::MacroSupport::new(self); 91 | let url = support.create_url(path)?; 92 | #query 93 | 94 | let response = #execute_request ?; 95 | pretend::internal::IntoResponse::into_response(response) 96 | } 97 | }) 98 | } 99 | -------------------------------------------------------------------------------- /pretend-codegen/src/errors.rs: -------------------------------------------------------------------------------- 1 | use std::iter::FromIterator; 2 | use syn::{Error, Result}; 3 | 4 | pub(crate) const CODEGEN_FAILURE: &str = "Failed to generate pretend implementation"; 5 | pub(crate) const METHOD_FAILURE: &str = "Failed to generate method implementation"; 6 | pub(crate) const INVALID_ATTR: &str = "Expected `#[pretend]` or `#[pretend(?Send)]`"; 7 | pub(crate) const UNSUPPORTED_ATTR_SYNC: &str = 8 | "`?Send` is not supported for blocking implementation"; 9 | pub(crate) const NO_METHOD: &str = "Please declare at least one method for this trait"; 10 | pub(crate) const INCONSISTENT_ASYNC: &str = 11 | "Unable to deduce if this trait is async or not. Please mark either all methods or none as async."; 12 | pub(crate) const INCONSISTENT_ASYNC_ASYNC_HINT: &str = "async method defined here"; 13 | pub(crate) const INCONSISTENT_ASYNC_NON_ASYNC_HINT: &str = "non-async method defined here"; 14 | pub(crate) const UNSUPPORTED_TRAIT_ITEM: &str = "Only methods are supported"; 15 | pub(crate) const UNSUPPORTED_GENERICS: &str = "Generics are not supported"; 16 | pub(crate) const UNSUPPORTED_RECEIVER: &str = "Method must take `&self` as receiver"; 17 | pub(crate) const MISSING_REQUEST: &str = "Method must have the `#[request]` attribute"; 18 | pub(crate) const TOO_MANY_REQUESTS: &str = "Method must have the `#[request]` attribute only once"; 19 | pub(crate) const TOO_MANY_REQUESTS_HINT: &str = "`#[request]` attribute defined here"; 20 | pub(crate) const INVALID_REQUEST: &str = 21 | "`#[request]` attribute must only have `method` and `path`"; 22 | pub(crate) const TOO_MANY_BODIES: &str = "Method can only have at most one body parameter"; 23 | pub(crate) const TOO_MANY_BODIES_HINT: &str = "Body parameter defined here"; 24 | pub(crate) const INVALID_HEADER: &str = "`#[header]` attribute must only have `name` and `value`"; 25 | 26 | pub(crate) struct Report { 27 | values: Vec, 28 | errors: Vec, 29 | } 30 | 31 | pub(crate) trait ErrorsExt { 32 | fn into_result(self, new_err: F) -> Result 33 | where 34 | F: FnOnce() -> Error; 35 | } 36 | 37 | impl Report { 38 | pub(crate) fn into_result(self, new_err: F) -> Result> 39 | where 40 | F: FnOnce() -> Error, 41 | { 42 | if self.errors.is_empty() { 43 | Ok(self.values) 44 | } else { 45 | self.errors.into_result(new_err) 46 | } 47 | } 48 | } 49 | 50 | impl FromIterator> for Report { 51 | fn from_iter(iter: I) -> Self 52 | where 53 | I: IntoIterator>, 54 | { 55 | let mut values = Vec::new(); 56 | let mut errors = Vec::new(); 57 | 58 | for item in iter { 59 | match item { 60 | Ok(item) => values.push(item), 61 | Err(err) => errors.push(err), 62 | } 63 | } 64 | 65 | Report { values, errors } 66 | } 67 | } 68 | 69 | impl ErrorsExt for Vec { 70 | fn into_result(self, new_err: F) -> Result 71 | where 72 | F: FnOnce() -> Error, 73 | { 74 | let mut errors = new_err(); 75 | for err in self { 76 | errors.combine(err) 77 | } 78 | Err(errors) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous integration 2 | on: 3 | push: 4 | branches: [ main ] 5 | pull_request: 6 | 7 | jobs: 8 | test: 9 | strategy: 10 | matrix: 11 | os: 12 | - ubuntu-latest 13 | - macos-latest 14 | - windows-latest 15 | toolchain: 16 | - stable 17 | - beta 18 | exclude: 19 | - os: macos-latest 20 | toolchain: beta 21 | - os: windows-latest 22 | toolchain: beta 23 | runs-on: ${{ matrix.os }} 24 | steps: 25 | - uses: actions/checkout@v2 26 | 27 | - uses: actions-rs/toolchain@v1 28 | with: 29 | profile: minimal 30 | toolchain: ${{ matrix.toolchain }} 31 | default: true 32 | 33 | - run: cargo test 34 | 35 | test-default-features-build: 36 | runs-on: ubuntu-latest 37 | steps: 38 | - uses: actions/checkout@v2 39 | 40 | - uses: actions-rs/toolchain@v1 41 | with: 42 | profile: minimal 43 | toolchain: stable 44 | default: true 45 | 46 | - run: cd tests/default-features-build && cargo test 47 | 48 | test-doc-propagation: 49 | runs-on: ubuntu-latest 50 | steps: 51 | - uses: actions/checkout@v2 52 | 53 | - uses: actions-rs/toolchain@v1 54 | with: 55 | profile: minimal 56 | toolchain: stable 57 | default: true 58 | 59 | - run: cd tests/doc-propagation && cargo test 60 | 61 | test-msrv-check: 62 | runs-on: ubuntu-latest 63 | steps: 64 | - uses: actions/checkout@v2 65 | 66 | - uses: actions-rs/toolchain@v1 67 | with: 68 | profile: minimal 69 | toolchain: "1.44.0" 70 | default: true 71 | 72 | - run: cd tests/msrv-check && cargo build 73 | env: 74 | CARGO_NET_GIT_FETCH_WITH_CLI: 'true' 75 | 76 | analyze: 77 | runs-on: ubuntu-latest 78 | steps: 79 | - uses: actions/checkout@v2 80 | 81 | - uses: actions-rs/toolchain@v1 82 | with: 83 | profile: minimal 84 | toolchain: nightly 85 | components: rustfmt, clippy 86 | default: true 87 | 88 | - name: rustfmt 89 | run: cargo fmt --all -- --check 90 | 91 | - name: Check Clippy lints 92 | uses: actions-rs/clippy-check@v1 93 | with: 94 | token: ${{ secrets.GITHUB_TOKEN }} 95 | 96 | - name: Generate code coverage report 97 | uses: actions-rs/tarpaulin@v0.1 98 | with: 99 | version: "0.16.0" 100 | args: "--exclude-files pretend/examples/*.rs --exclude-files pretend/tests/*.rs --exclude-files pretend/tests/builds/*.rs --exclude-files pretend-codegen/*.rs --exclude-files pretend-codegen/method/*.rs --exclude-files pretend-codegen/utils/*.rs --exclude-files tests/default-features-build/src/*.rs --exclude-files tests/default-features-build/tests/*.rs --exclude-files tests/default-features-build/tests/builds/*.rs" 101 | 102 | - name: Upload coverage to Codecov 103 | uses: codecov/codecov-action@v1 104 | with: 105 | token: ${{ secrets.CODECOV_TOKEN }} 106 | fail_ci_if_error: false 107 | -------------------------------------------------------------------------------- /pretend/tests/test_pretend.rs: -------------------------------------------------------------------------------- 1 | mod runtimes; 2 | mod server; 3 | 4 | use pretend::{pretend, Json, Pretend, Result, Url}; 5 | use pretend_reqwest::Client; 6 | use std::collections::HashMap; 7 | 8 | #[pretend] 9 | trait TestApi { 10 | #[request(method = "GET", path = "/method")] 11 | async fn get(&self) -> Result; 12 | #[request(method = "POST", path = "/method")] 13 | async fn post(&self) -> Result; 14 | #[request(method = "PUT", path = "/method")] 15 | async fn put(&self) -> Result; 16 | #[request(method = "PATCH", path = "/method")] 17 | async fn patch(&self) -> Result; 18 | #[request(method = "DELETE", path = "/method")] 19 | async fn delete(&self) -> Result; 20 | #[request(method = "GET", path = "/query")] 21 | async fn query(&self, query: &server::TestData) -> Result>>; 22 | #[request(method = "GET", path = "/headers")] 23 | #[header(name = "X-Test-Header-1", value = "abc")] 24 | #[header(name = "X-Test-Header-2", value = "{value}")] 25 | #[header(name = "X-{custom}", value = "custom")] 26 | async fn headers(&self, value: i32, custom: &str) -> Result>>; 27 | #[request(method = "POST", path = "/post/string")] 28 | #[header(name = "Content-Type", value = "text/plain")] 29 | async fn post_string(&self, body: &'static str) -> Result; 30 | #[request(method = "POST", path = "/post/json")] 31 | async fn post_json(&self, json: &server::TestData) -> Result>; 32 | #[request(method = "POST", path = "/post/form")] 33 | async fn post_form(&self, form: &server::TestData) -> Result>; 34 | } 35 | 36 | fn new_pretend() -> impl TestApi { 37 | let url = Url::parse(server::URL).unwrap(); 38 | let client = Client::default(); 39 | Pretend::for_client(client).with_url(url) 40 | } 41 | 42 | #[test] 43 | fn test_pretend() { 44 | server::test(|| { 45 | runtimes::block_on(async { 46 | test_get().await; 47 | test_post().await; 48 | test_put().await; 49 | test_patch().await; 50 | test_delete().await; 51 | 52 | test_query().await; 53 | test_headers().await; 54 | test_post_string().await; 55 | test_post_json().await; 56 | test_post_form().await; 57 | }) 58 | }); 59 | } 60 | 61 | async fn test_get() { 62 | let result = new_pretend().get().await; 63 | assert!(result.is_ok()); 64 | } 65 | 66 | async fn test_post() { 67 | let result = new_pretend().post().await; 68 | assert!(result.is_ok()); 69 | } 70 | 71 | async fn test_put() { 72 | let result = new_pretend().put().await; 73 | assert!(result.is_ok()); 74 | } 75 | 76 | async fn test_patch() { 77 | let result = new_pretend().patch().await; 78 | assert!(result.is_ok()); 79 | } 80 | 81 | async fn test_delete() { 82 | let result = new_pretend().delete().await; 83 | assert!(result.is_ok()); 84 | } 85 | 86 | async fn test_query() { 87 | let query = server::TestData { 88 | first: "Hello".to_string(), 89 | second: 123, 90 | }; 91 | 92 | let expected_args = [("first", "Hello"), ("second", "123")] 93 | .iter() 94 | .map(|(key, value)| (key.to_string(), value.to_string())) 95 | .collect::>(); 96 | 97 | let result = new_pretend().query(&query).await.unwrap(); 98 | assert_eq!(result.value(), expected_args); 99 | } 100 | 101 | async fn test_headers() { 102 | let result = new_pretend().headers(123, "test").await.unwrap(); 103 | let headers = result.value(); 104 | assert_eq!(headers.get("x-test-header-1").unwrap(), "abc"); 105 | assert_eq!(headers.get("x-test-header-2").unwrap(), "123"); 106 | assert_eq!(headers.get("x-test").unwrap(), "custom"); 107 | } 108 | 109 | async fn test_post_string() { 110 | let expected = "Hello"; 111 | let result = new_pretend().post_string(expected).await.unwrap(); 112 | assert_eq!(expected, result); 113 | } 114 | 115 | async fn test_post_json() { 116 | let json = server::TestData { 117 | first: "Hello".to_string(), 118 | second: 123, 119 | }; 120 | let result = new_pretend().post_json(&json).await.unwrap(); 121 | assert_eq!(result.value(), json); 122 | } 123 | 124 | async fn test_post_form() { 125 | let json = server::TestData { 126 | first: "Hello".to_string(), 127 | second: 123, 128 | }; 129 | let result = new_pretend().post_form(&json).await.unwrap(); 130 | assert_eq!(result.value(), json); 131 | } 132 | -------------------------------------------------------------------------------- /pretend/tests/server.rs: -------------------------------------------------------------------------------- 1 | use actix_web::dev::Server; 2 | use actix_web::http::{HeaderName, HeaderValue, StatusCode}; 3 | use actix_web::web::{Either, Form, HttpRequest, Json, Path, Query}; 4 | use actix_web::{delete, get, patch, post, put, App, HttpResponse, HttpServer, Responder}; 5 | use serde::{Deserialize, Serialize}; 6 | use std::collections::HashMap; 7 | use std::convert::TryFrom; 8 | use std::io; 9 | use std::sync::mpsc::{channel, Sender}; 10 | use std::thread::{spawn, JoinHandle}; 11 | 12 | pub const URL: &str = "http://localhost:9999"; 13 | 14 | #[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] 15 | pub struct TestData { 16 | pub first: String, 17 | pub second: i32, 18 | } 19 | 20 | #[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] 21 | pub struct ErrorData { 22 | pub message: String, 23 | } 24 | 25 | const HELLO_WORLD: &str = "Hello World"; 26 | const ERROR: &str = "Error"; 27 | 28 | #[get("/method")] 29 | async fn method_get() -> impl Responder { 30 | "GET" 31 | } 32 | 33 | #[post("/method")] 34 | async fn method_post() -> impl Responder { 35 | "POST" 36 | } 37 | 38 | #[put("/method")] 39 | async fn method_put() -> impl Responder { 40 | "PUT" 41 | } 42 | 43 | #[patch("/method")] 44 | async fn method_patch() -> impl Responder { 45 | "PATCH" 46 | } 47 | 48 | #[delete("/method")] 49 | async fn method_delete() -> impl Responder { 50 | "DELETE" 51 | } 52 | 53 | #[get("/query")] 54 | async fn query(info: Query>) -> impl Responder { 55 | Json(info.0) 56 | } 57 | 58 | #[post("/post/string")] 59 | async fn post_with_string(body: String) -> impl Responder { 60 | body 61 | } 62 | 63 | #[post("/post/json")] 64 | async fn post_with_json(json: Json) -> impl Responder { 65 | json 66 | } 67 | 68 | #[post("/post/form")] 69 | async fn post_with_form(form: Form) -> impl Responder { 70 | Json(form.0) 71 | } 72 | 73 | #[get("/{status}/text")] 74 | async fn get_text(status: Path) -> impl Responder { 75 | let response = if status.0 < 400 { HELLO_WORLD } else { ERROR }; 76 | HttpResponse::build(StatusCode::try_from(status.0).unwrap()) 77 | .content_type("plain/text") 78 | .header("x-lovely", "yes") 79 | .body(response) 80 | } 81 | 82 | #[get("/{status}/json")] 83 | async fn get_json(status: Path) -> impl Responder { 84 | let mut builder = HttpResponse::build(StatusCode::try_from(status.0).unwrap()); 85 | let builder = builder 86 | .content_type("application/json") 87 | .header("x-lovely", "yes"); 88 | 89 | if status.0 < 400 { 90 | Either::A(builder.json(TestData { 91 | first: "Hello".to_string(), 92 | second: 123, 93 | })) 94 | } else { 95 | Either::B(builder.json(ErrorData { 96 | message: "Error".to_string(), 97 | })) 98 | } 99 | } 100 | 101 | fn map_headers((n, v): (&HeaderName, &HeaderValue)) -> Option<(String, String)> { 102 | let n = n.to_string(); 103 | let v = v.to_str().ok()?; 104 | Some((n, v.to_string())) 105 | } 106 | 107 | #[get("/headers")] 108 | async fn headers(request: HttpRequest) -> impl Responder { 109 | let headers = request 110 | .headers() 111 | .iter() 112 | .filter_map(map_headers) 113 | .collect::>(); 114 | Json(headers) 115 | } 116 | 117 | pub struct ServerRunner { 118 | server: Server, 119 | handle: JoinHandle>, 120 | } 121 | 122 | impl ServerRunner { 123 | fn start() -> Self { 124 | let (send, recv) = channel(); 125 | let handle = spawn(move || Self::run_server(send)); 126 | let server = recv.recv().unwrap(); 127 | 128 | ServerRunner { server, handle } 129 | } 130 | 131 | fn stop(self) { 132 | Self::stop_server(self.server); 133 | self.handle.join().unwrap().unwrap(); 134 | } 135 | 136 | #[actix_web::main] 137 | async fn run_server(send: Sender) -> io::Result<()> { 138 | let supplier = || { 139 | App::new() 140 | .service(method_get) 141 | .service(method_post) 142 | .service(method_put) 143 | .service(method_patch) 144 | .service(method_delete) 145 | .service(query) 146 | .service(headers) 147 | .service(post_with_string) 148 | .service(post_with_json) 149 | .service(post_with_form) 150 | .service(get_text) 151 | .service(get_json) 152 | }; 153 | let http_server = HttpServer::new(supplier) 154 | .bind("localhost:9999")? 155 | .shutdown_timeout(5); 156 | let server = http_server.run(); 157 | send.send(server.clone()).unwrap(); 158 | server.await 159 | } 160 | 161 | #[actix_web::main] 162 | async fn stop_server(server: Server) { 163 | server.stop(true).await; 164 | } 165 | } 166 | 167 | pub fn test(f: F) 168 | where 169 | F: FnOnce(), 170 | { 171 | let server = ServerRunner::start(); 172 | f(); 173 | server.stop(); 174 | } 175 | -------------------------------------------------------------------------------- /pretend/tests/clients_tester.rs: -------------------------------------------------------------------------------- 1 | use pretend::client::{BlockingClient, Bytes, Client, LocalClient, Method}; 2 | use pretend::http::HeaderValue; 3 | use pretend::{HeaderMap, Response, Result, Url}; 4 | use std::collections::HashMap; 5 | use tokio::runtime::Runtime; 6 | 7 | pub trait TestableClient { 8 | fn execute( 9 | &self, 10 | method: Method, 11 | url: Url, 12 | headers: HeaderMap, 13 | body: Option, 14 | ) -> Result>; 15 | } 16 | 17 | impl TestableClient for C 18 | where 19 | C: BlockingClient, 20 | { 21 | fn execute( 22 | &self, 23 | method: Method, 24 | url: Url, 25 | headers: HeaderMap, 26 | body: Option, 27 | ) -> Result> { 28 | C::execute(self, method, url, headers, body) 29 | } 30 | } 31 | 32 | pub struct TokioTestableClient 33 | where 34 | C: Client, 35 | { 36 | client: C, 37 | runtime: Runtime, 38 | } 39 | 40 | impl TokioTestableClient 41 | where 42 | C: Client, 43 | { 44 | pub fn new(client: C, runtime: Runtime) -> Self { 45 | TokioTestableClient { client, runtime } 46 | } 47 | } 48 | 49 | impl TestableClient for TokioTestableClient 50 | where 51 | C: Client, 52 | { 53 | fn execute( 54 | &self, 55 | method: Method, 56 | url: Url, 57 | headers: HeaderMap, 58 | body: Option, 59 | ) -> Result> { 60 | let future = async { self.client.execute(method, url, headers, body).await }; 61 | self.runtime.block_on(future) 62 | } 63 | } 64 | 65 | pub struct TokioTestableLocalClient 66 | where 67 | C: LocalClient, 68 | { 69 | client: C, 70 | runtime: Runtime, 71 | } 72 | 73 | impl TokioTestableLocalClient 74 | where 75 | C: LocalClient, 76 | { 77 | pub fn new(client: C, runtime: Runtime) -> Self { 78 | TokioTestableLocalClient { client, runtime } 79 | } 80 | } 81 | 82 | impl TestableClient for TokioTestableLocalClient 83 | where 84 | C: LocalClient, 85 | { 86 | fn execute( 87 | &self, 88 | method: Method, 89 | url: Url, 90 | headers: HeaderMap, 91 | body: Option, 92 | ) -> Result> { 93 | let future = async { self.client.execute(method, url, headers, body).await }; 94 | self.runtime.block_on(future) 95 | } 96 | } 97 | 98 | pub struct ClientsTester { 99 | url: Url, 100 | clients: Vec>, 101 | } 102 | 103 | impl ClientsTester { 104 | pub fn new(url: Url, clients: Vec>) -> Self { 105 | ClientsTester { url, clients } 106 | } 107 | 108 | pub fn test(self) { 109 | for client in &self.clients { 110 | self.test_methods(Box::as_ref(client)); 111 | self.test_headers(Box::as_ref(client)); 112 | self.test_bodies(Box::as_ref(client)); 113 | } 114 | } 115 | 116 | fn test_methods(&self, client: &dyn TestableClient) { 117 | self.test_method(client, Method::GET); 118 | self.test_method(client, Method::POST); 119 | self.test_method(client, Method::PUT); 120 | self.test_method(client, Method::PATCH); 121 | self.test_method(client, Method::DELETE); 122 | } 123 | 124 | fn test_method(&self, client: &dyn TestableClient, method: Method) { 125 | let method_name = method.to_string(); 126 | let url = self.url.join("/method").unwrap(); 127 | 128 | let response = client.execute(method, url, HeaderMap::new(), None).unwrap(); 129 | 130 | assert_eq!(response.status().as_u16(), 200); 131 | let body = String::from_utf8(response.body().to_vec()).unwrap(); 132 | assert_eq!(body, method_name); 133 | } 134 | 135 | fn test_headers(&self, client: &dyn TestableClient) { 136 | let url = self.url.join("/headers").unwrap(); 137 | let mut headers = HeaderMap::new(); 138 | headers.append("X-Test-Header-1", HeaderValue::from_static("abc")); 139 | headers.append("X-Test-Header-2", HeaderValue::from_static("123")); 140 | let response = client.execute(Method::GET, url, headers, None).unwrap(); 141 | 142 | assert_eq!(response.status().as_u16(), 200); 143 | let headers: HashMap = 144 | serde_json::from_slice(response.body().as_ref()).unwrap(); 145 | assert_eq!(headers.get("x-test-header-1").unwrap(), "abc"); 146 | assert_eq!(headers.get("x-test-header-2").unwrap(), "123"); 147 | } 148 | 149 | fn test_bodies(&self, client: &dyn TestableClient) { 150 | let expected = "Hello World"; 151 | 152 | let url = self.url.join("/post/string").unwrap(); 153 | let mut headers = HeaderMap::new(); 154 | headers.append("Content-Type", HeaderValue::from_static("text/plain")); 155 | let body = Some(Bytes::from(expected)); 156 | let response = client.execute(Method::POST, url, headers, body).unwrap(); 157 | 158 | assert_eq!(response.status().as_u16(), 200); 159 | let body = String::from_utf8(response.body().to_vec()).unwrap(); 160 | assert_eq!(body, expected); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /pretend/examples/responses.rs: -------------------------------------------------------------------------------- 1 | use pretend::{pretend, Json, JsonResult, Pretend, Response, Result, Url}; 2 | use pretend_reqwest::Client; 3 | use serde::Deserialize; 4 | 5 | // This example show how to receive various response types. 6 | // It uses the Github API (that returns JSON) 7 | 8 | #[derive(Clone, Debug, Deserialize)] 9 | struct Contributor { 10 | login: String, 11 | } 12 | 13 | type Contributors = Vec; 14 | 15 | #[derive(Clone, Debug, Deserialize)] 16 | struct GithubError { 17 | message: String, 18 | } 19 | 20 | type ContributorsResult = JsonResult; 21 | 22 | #[pretend] 23 | trait Github { 24 | #[request(method = "GET", path = "/repos/SfietKonstantin/{repo}/contributors")] 25 | #[header(name = "User-Agent", value = "pretend example")] 26 | async fn string(&self, repo: &str) -> Result; 27 | 28 | #[request(method = "GET", path = "/repos/SfietKonstantin/{repo}/contributors")] 29 | #[header(name = "User-Agent", value = "pretend example")] 30 | async fn string_response(&self, repo: &str) -> Result>; 31 | 32 | #[request(method = "GET", path = "/repos/SfietKonstantin/{repo}/contributors")] 33 | #[header(name = "User-Agent", value = "pretend example")] 34 | async fn bytes(&self, repo: &str) -> Result>; 35 | 36 | #[request(method = "GET", path = "/repos/SfietKonstantin/{repo}/contributors")] 37 | #[header(name = "User-Agent", value = "pretend example")] 38 | async fn bytes_response(&self, repo: &str) -> Result>>; 39 | 40 | #[request(method = "GET", path = "/repos/SfietKonstantin/{repo}/contributors")] 41 | #[header(name = "User-Agent", value = "pretend example")] 42 | async fn json(&self, repo: &str) -> Result>; 43 | 44 | #[request(method = "GET", path = "/repos/SfietKonstantin/{repo}/contributors")] 45 | #[header(name = "User-Agent", value = "pretend example")] 46 | async fn json_response(&self, repo: &str) -> Result>>; 47 | 48 | #[request(method = "GET", path = "/repos/SfietKonstantin/{repo}/contributors")] 49 | #[header(name = "User-Agent", value = "pretend example")] 50 | async fn json_result(&self, repo: &str) -> Result; 51 | 52 | #[request(method = "GET", path = "/repos/SfietKonstantin/{repo}/contributors")] 53 | #[header(name = "User-Agent", value = "pretend example")] 54 | async fn json_result_response(&self, repo: &str) -> Result>; 55 | } 56 | 57 | fn create_pretend() -> impl Github { 58 | let url = Url::parse("https://api.github.com").unwrap(); 59 | Pretend::for_client(Client::default()).with_url(url) 60 | } 61 | 62 | #[tokio::main] 63 | async fn main() { 64 | let pretend = create_pretend(); 65 | 66 | // The following successful calls will return different kind of responses. 67 | // Either bodies are return alone, or with status and headers by using Response. 68 | let result = pretend.string("pretend").await.unwrap(); 69 | println!("{}", result); 70 | 71 | let result = pretend.string_response("pretend").await.unwrap(); 72 | println!("HTTP {}, {}", result.status(), result.body()); 73 | 74 | let result = pretend.bytes("pretend").await.unwrap(); 75 | let body = String::from_utf8_lossy(&result); 76 | println!("{}", body); 77 | 78 | let result = pretend.bytes_response("pretend").await.unwrap(); 79 | let body = String::from_utf8_lossy(result.body()); 80 | println!("HTTP {}, {}", result.status(), body); 81 | 82 | let result = pretend.json("pretend").await.unwrap(); 83 | println!("{:?}", result.value()); 84 | 85 | let result = pretend.json_response("pretend").await.unwrap(); 86 | println!("HTTP {}, {:?}", result.status(), result.body()); 87 | 88 | let result = pretend.json_result("pretend").await.unwrap(); 89 | println!("{:?}", result); 90 | 91 | let result = pretend.json_result_response("pretend").await.unwrap(); 92 | println!("HTTP {}, {:?}", result.status(), result.body()); 93 | 94 | // The following calls will fail for a non-existing repo 95 | 96 | // These calls fail on an error, as they only return a body when successful. 97 | let result = pretend.string("non-existing").await; 98 | assert!(result.is_err()); 99 | 100 | let result = pretend.bytes("non-existing").await; 101 | assert!(result.is_err()); 102 | 103 | let result = pretend.json("non-existing").await; 104 | assert!(result.is_err()); 105 | 106 | // These calls returns a Response. It is possible to inspect the status 107 | // except for Json, as body deserialization will fail. 108 | let result = pretend.string_response("non-existing").await.unwrap(); 109 | assert_eq!(result.status().as_u16(), 404); 110 | 111 | let result = pretend.bytes_response("non-existing").await.unwrap(); 112 | assert_eq!(result.status().as_u16(), 404); 113 | 114 | let result = pretend.json_response("non-existing").await; 115 | assert!(result.is_err()); 116 | 117 | // By using JsonResult, a different body can be returned on error 118 | let result = pretend.json_result("non-existing").await.unwrap(); 119 | println!("{:?}", result); 120 | 121 | let result = pretend.json_result_response("non-existing").await.unwrap(); 122 | println!("HTTP {}, {:?}", result.status(), result.body()); 123 | } 124 | -------------------------------------------------------------------------------- /pretend-codegen/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod attr; 2 | mod errors; 3 | mod format; 4 | mod method; 5 | mod utils; 6 | 7 | use crate::attr::PretendAttr; 8 | use crate::errors::{ 9 | ErrorsExt, Report, CODEGEN_FAILURE, INCONSISTENT_ASYNC, INCONSISTENT_ASYNC_ASYNC_HINT, 10 | INCONSISTENT_ASYNC_NON_ASYNC_HINT, NO_METHOD, UNSUPPORTED_ATTR_SYNC, 11 | }; 12 | use crate::method::{trait_item, trait_item_implem}; 13 | use crate::utils::WithTokens; 14 | use proc_macro::TokenStream; 15 | use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; 16 | use quote::quote; 17 | use syn::{parse_macro_input, Error, ItemTrait, Result, Signature, TraitItem}; 18 | 19 | #[proc_macro_attribute] 20 | pub fn pretend(attr: TokenStream, item: TokenStream) -> TokenStream { 21 | let attr = parse_macro_input!(attr as PretendAttr); 22 | let item = parse_macro_input!(item as ItemTrait); 23 | implement_pretend(attr, item) 24 | .unwrap_or_else(Error::into_compile_error) 25 | .into() 26 | } 27 | 28 | fn implement_pretend(attr: PretendAttr, item: ItemTrait) -> Result { 29 | let name = &item.ident; 30 | let vis = &item.vis; 31 | let items = &item.items; 32 | let attrs = &item.attrs; 33 | let trait_items = items.iter().map(trait_item).collect::>(); 34 | 35 | let kind = parse_client_kind(name, attr, items)?; 36 | let methods = items 37 | .iter() 38 | .map(|item| trait_item_implem(item, &kind)) 39 | .collect::>() 40 | .into_result(|| Error::new(Span::call_site(), CODEGEN_FAILURE))?; 41 | 42 | let attr = async_trait_attr(&kind); 43 | let client = client_implem(&kind); 44 | let send_sync = send_sync_traits_impl(&kind); 45 | let tokens = quote! { 46 | #attr 47 | #(#attrs)* 48 | #vis trait #name { 49 | #(#trait_items)* 50 | } 51 | 52 | #attr 53 | impl #name for pretend::Pretend 54 | where C: #client #send_sync, 55 | R: pretend::resolver::ResolveUrl #send_sync, 56 | I: pretend::interceptor::InterceptRequest #send_sync, 57 | { 58 | #(#methods)* 59 | } 60 | }; 61 | Ok(tokens) 62 | } 63 | 64 | enum ClientKind { 65 | Async, 66 | AsyncLocal, 67 | Blocking, 68 | } 69 | 70 | fn parse_client_kind(name: &Ident, attr: PretendAttr, items: &[TraitItem]) -> Result { 71 | let asyncs = items.iter().filter_map(is_method_async).collect::>(); 72 | let is_async = asyncs.iter().all(|item| item.value); 73 | let is_not_async = asyncs.iter().all(|item| !item.value); 74 | 75 | match (is_async, is_not_async) { 76 | (true, false) => { 77 | if attr.local { 78 | Ok(ClientKind::AsyncLocal) 79 | } else { 80 | Ok(ClientKind::Async) 81 | } 82 | } 83 | (false, true) => { 84 | if attr.local { 85 | Err(Error::new(Span::call_site(), UNSUPPORTED_ATTR_SYNC)) 86 | } else { 87 | Ok(ClientKind::Blocking) 88 | } 89 | } 90 | _ => { 91 | if asyncs.is_empty() { 92 | Err(Error::new_spanned(name, NO_METHOD)) 93 | } else { 94 | let async_hints = asyncs 95 | .iter() 96 | .filter(|item| item.value) 97 | .map(|item| Error::new_spanned(item.tokens, INCONSISTENT_ASYNC_ASYNC_HINT)); 98 | 99 | let non_async_hints = asyncs 100 | .iter() 101 | .filter(|item| !item.value) 102 | .map(|item| Error::new_spanned(item.tokens, INCONSISTENT_ASYNC_NON_ASYNC_HINT)); 103 | 104 | let errors = async_hints.chain(non_async_hints).collect::>(); 105 | errors.into_result(|| Error::new_spanned(name, INCONSISTENT_ASYNC)) 106 | } 107 | } 108 | } 109 | } 110 | 111 | fn is_method_async(item: &TraitItem) -> Option> { 112 | match item { 113 | TraitItem::Method(method) => { 114 | let is_async = method.sig.asyncness.is_some(); 115 | Some(WithTokens::new(is_async, &method.sig)) 116 | } 117 | _ => None, 118 | } 119 | } 120 | 121 | fn async_trait_attr(kind: &ClientKind) -> TokenStream2 { 122 | match kind { 123 | ClientKind::Async => quote! { 124 | #[pretend::client::async_trait] 125 | }, 126 | ClientKind::AsyncLocal => quote! { 127 | #[pretend::client::async_trait(?Send)] 128 | }, 129 | ClientKind::Blocking => TokenStream2::new(), 130 | } 131 | } 132 | 133 | fn client_implem(kind: &ClientKind) -> TokenStream2 { 134 | match kind { 135 | ClientKind::Async => quote! { 136 | pretend::client::Client 137 | }, 138 | ClientKind::AsyncLocal => quote! { 139 | pretend::client::LocalClient 140 | }, 141 | ClientKind::Blocking => quote! { 142 | pretend::client::BlockingClient 143 | }, 144 | } 145 | } 146 | 147 | fn send_sync_traits_impl(kind: &ClientKind) -> TokenStream2 { 148 | match kind { 149 | ClientKind::Async => quote! { 150 | + Send + Sync 151 | }, 152 | ClientKind::AsyncLocal => TokenStream2::new(), 153 | ClientKind::Blocking => TokenStream2::new(), 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /pretend/README.md: -------------------------------------------------------------------------------- 1 | # pretend 2 | 3 | `pretend` is a modular, [Feign]-inspired HTTP, client based on macros. It's goal is to decouple 4 | the definition of a REST API from it's implementation. 5 | 6 | 7 | Some features: 8 | - Declarative 9 | - Asynchronous-first implementations 10 | - HTTP client agnostic 11 | - JSON support thanks to serde 12 | 13 | [Feign]: https://github.com/OpenFeign/feign 14 | 15 | ## Getting started 16 | 17 | A REST API is described by annotating a trait: 18 | 19 | ```rust 20 | use pretend::{pretend, request, Result}; 21 | 22 | #[pretend] 23 | trait HttpBin { 24 | #[request(method = "POST", path = "/anything")] 25 | async fn post_anything(&self, body: &'static str) -> Result; 26 | } 27 | ``` 28 | 29 | Under the hood, `pretend` will implement this trait for `Pretend`. An instance of this 30 | struct can be constructed by passing a client implementation, and the REST API's base url. In 31 | the following example, we are using the `reqwest` based client. 32 | 33 | ```rust 34 | use pretend::{Pretend, Url}; 35 | use pretend_reqwest::Client; 36 | 37 | #[tokio::main] 38 | async fn main() { 39 | let client = Client::default(); 40 | let url = Url::parse("https://httpbin.org").unwrap(); 41 | let pretend = Pretend::for_client(client).with_url(url); 42 | let response = pretend.post_anything("hello").await.unwrap(); 43 | assert!(response.contains("hello")); 44 | } 45 | ``` 46 | 47 | ## Sending headers, query parameters and bodies 48 | 49 | Headers are provided as attributes using `header`. 50 | 51 | ```rust 52 | use pretend::{header, pretend, request, Result}; 53 | 54 | #[pretend] 55 | trait HttpBin { 56 | #[request(method = "GET", path = "/get")] 57 | #[header(name = "X-Test-Header-1", value = "abc")] 58 | #[header(name = "X-Test-Header-2", value = "other")] 59 | async fn get_with_headers(&self, value: i32, custom: &str) -> Result<()>; 60 | } 61 | ``` 62 | 63 | Query parameters and bodies are provided as method parameters. Body type is guessed based on 64 | the parameter name: 65 | 66 | - Parameter `body` will be sent as raw bytes. This requires the body to have 'static lifetime. 67 | - Parameter `form` will be serialized as form-encoded using `serde`. 68 | - Parameter `json` will be serialized as JSON using `serde`. 69 | 70 | Query parameter is passed with the `query` parameter. It is also serialized using `serde`. 71 | 72 | ```rust 73 | use pretend::{pretend, request, Json, Result, Serialize}; 74 | 75 | #[derive(Serialize)] 76 | struct Data { 77 | value: i32, 78 | } 79 | 80 | #[pretend] 81 | trait HttpBin { 82 | #[request(method = "POST", path = "/anything")] 83 | async fn post_bytes(&self, body: &'static [u8]) -> Result<()>; 84 | 85 | #[request(method = "POST", path = "/anything")] 86 | async fn post_string(&self, body: &'static str) -> Result<()>; 87 | 88 | #[request(method = "POST", path = "/anything")] 89 | async fn post_with_query_params(&self, query: &Data) -> Result<()>; 90 | 91 | #[request(method = "POST", path = "/anything")] 92 | async fn post_json(&self, json: &Data) -> Result<()>; 93 | } 94 | ``` 95 | 96 | ## Handling responses 97 | 98 | `pretend` support a wide range of response types, based on the return type of the method. 99 | The body can be returned as a `Vec`, a string or as JSON by using the `Json` wrapper 100 | type. The unit type `()` can also be used if the body should be discarded. 101 | 102 | `JsonResult` is also offered as a convenience type. It will deserialize into a value type 103 | or an error type depending on the HTTP status code. 104 | 105 | When retrieving body alone, an HTTP error will cause the method to return an error. It is 106 | possible to prevent the method to fail and access the HTTP status code by wrapping these 107 | types inside a `Response`. This also allows accessing response headers. 108 | 109 | ```rust 110 | use pretend::{pretend, request, Deserialize, Json, JsonResult, Response, Result}; 111 | 112 | #[derive(Deserialize)] 113 | struct Data { 114 | value: i32, 115 | } 116 | 117 | #[derive(Deserialize)] 118 | struct Error { 119 | error: String, 120 | } 121 | 122 | #[pretend] 123 | trait HttpBin { 124 | #[request(method = "POST", path = "/anything")] 125 | async fn read_bytes(&self) -> Result>; 126 | 127 | #[request(method = "POST", path = "/anything")] 128 | async fn read_string(&self) -> Result; 129 | 130 | #[request(method = "POST", path = "/anything")] 131 | async fn read_json(&self) -> Result>; 132 | 133 | #[request(method = "POST", path = "/anything")] 134 | async fn read_json_result(&self) -> Result>; 135 | 136 | #[request(method = "POST", path = "/anything")] 137 | async fn read_status(&self) -> Result>; 138 | } 139 | ``` 140 | 141 | ## Templating 142 | 143 | Request paths and headers support templating. A value between braces will be replaced by 144 | a parameter with the same name. The replacement is done with `format!`, meaning that 145 | any type that implement `Display` is supported. 146 | 147 | ```rust 148 | use pretend::{header, pretend, request, Deserialize, Json, Pretend, Result}; 149 | use pretend_reqwest::Client; 150 | use std::collections::HashMap; 151 | 152 | #[derive(Deserialize)] 153 | struct Data { 154 | url: String, 155 | headers: HashMap, 156 | } 157 | 158 | #[pretend] 159 | trait HttpBin { 160 | #[request(method = "POST", path = "/{path}")] 161 | #[header(name = "X-{header}", value = "{value}$")] 162 | async fn read(&self, path: &str, header: &str, value: i32) -> Result>; 163 | } 164 | 165 | #[tokio::main] 166 | async fn main() { 167 | let client = Client::default(); 168 | let url = Url::parse("https://httpbin.org").unwrap(); 169 | let pretend = Pretend::for_client(client).with_url(url); 170 | let response = pretend.read("anything", "My-Header", 123).await.unwrap(); 171 | let data = response.value(); 172 | assert_eq!(data.url, "https://httpbin.org/anything"); 173 | assert_eq!(*data.headers.get("X-My-Header").unwrap(), "123$".to_string()); 174 | } 175 | ``` 176 | 177 | ## Documentation 178 | 179 | For more information, please refer to the [API reference](https://docs.rs/pretend/latest/pretend/). 180 | -------------------------------------------------------------------------------- /pretend/tests/test_output.rs: -------------------------------------------------------------------------------- 1 | mod runtimes; 2 | mod server; 3 | 4 | use pretend::http::{HeaderValue, StatusCode}; 5 | use pretend::{pretend, Error, Json, JsonResult, Pretend, Response, Result, Url}; 6 | use pretend_reqwest::Client; 7 | 8 | type TestDataResult = JsonResult; 9 | 10 | #[pretend] 11 | trait TestApi { 12 | #[request(method = "GET", path = "/{status}/text")] 13 | async fn get_unit(&self, status: i32) -> Result<()>; 14 | #[request(method = "GET", path = "/{status}/text")] 15 | async fn get_unit_response(&self, status: i32) -> Result>; 16 | #[request(method = "GET", path = "/{status}/text")] 17 | async fn get_text(&self, status: i32) -> Result; 18 | #[request(method = "GET", path = "/{status}/text")] 19 | async fn get_text_response(&self, status: i32) -> Result>; 20 | #[request(method = "GET", path = "/{status}/text")] 21 | async fn get_bytes(&self, status: i32) -> Result>; 22 | #[request(method = "GET", path = "/{status}/text")] 23 | async fn get_bytes_response(&self, status: i32) -> Result>>; 24 | #[request(method = "GET", path = "/{status}/json")] 25 | async fn get_json(&self, status: i32) -> Result>; 26 | #[request(method = "GET", path = "/{status}/json")] 27 | async fn get_json_response(&self, status: i32) -> Result>>; 28 | #[request(method = "GET", path = "/{status}/json")] 29 | async fn get_json_result(&self, status: i32) -> Result; 30 | #[request(method = "GET", path = "/{status}/json")] 31 | async fn get_json_result_response(&self, status: i32) -> Result>; 32 | } 33 | 34 | fn new_pretend() -> impl TestApi { 35 | let url = Url::parse(server::URL).unwrap(); 36 | let client = Client::default(); 37 | Pretend::for_client(client).with_url(url) 38 | } 39 | 40 | fn get_err_status(result: Result) -> Option { 41 | match result { 42 | Err(Error::Status(status)) => Some(status.as_u16()), 43 | _ => None, 44 | } 45 | } 46 | 47 | fn is_body_err(result: Result) -> bool { 48 | match result { 49 | Err(Error::Body(_)) => true, 50 | _ => true, 51 | } 52 | } 53 | 54 | #[test] 55 | fn test_output() { 56 | server::test(|| { 57 | runtimes::block_on(async { 58 | test_status_unit().await; 59 | test_status_unit_response().await; 60 | test_status_text().await; 61 | test_status_text_response().await; 62 | test_status_bytes().await; 63 | test_status_bytes_response().await; 64 | test_status_json().await; 65 | test_status_json_response().await; 66 | test_status_json_result().await; 67 | test_status_json_result_response().await; 68 | }) 69 | }) 70 | } 71 | 72 | async fn test_status_unit() { 73 | let api = new_pretend(); 74 | 75 | let result = api.get_unit(200).await.unwrap(); 76 | assert_eq!(result, ()); 77 | 78 | let result = api.get_unit(402).await; 79 | assert_eq!(get_err_status(result), Some(402)); 80 | } 81 | 82 | async fn test_status_unit_response() { 83 | let api = new_pretend(); 84 | 85 | let expected_header = HeaderValue::from_str("yes").unwrap(); 86 | 87 | let result = api.get_unit_response(200).await.unwrap(); 88 | let header = result.headers().get("x-lovely").unwrap(); 89 | assert_eq!(*result.status(), StatusCode::from_u16(200).unwrap()); 90 | assert_eq!(*header, expected_header); 91 | assert_eq!(*result.body(), ()); 92 | 93 | let result = api.get_unit_response(402).await.unwrap(); 94 | let header = result.headers().get("x-lovely").unwrap(); 95 | assert_eq!(*result.status(), StatusCode::from_u16(402).unwrap()); 96 | assert_eq!(*header, expected_header); 97 | assert_eq!(*result.body(), ()); 98 | } 99 | 100 | async fn test_status_text() { 101 | let api = new_pretend(); 102 | 103 | let result = api.get_text(200).await.unwrap(); 104 | assert_eq!(result, "Hello World"); 105 | 106 | let result = api.get_text(402).await; 107 | assert_eq!(get_err_status(result), Some(402)); 108 | } 109 | 110 | async fn test_status_text_response() { 111 | let api = new_pretend(); 112 | 113 | let result = api.get_text_response(200).await.unwrap(); 114 | assert_eq!(*result.status(), StatusCode::from_u16(200).unwrap()); 115 | assert_eq!(result.body(), "Hello World"); 116 | 117 | let result = api.get_text_response(402).await.unwrap(); 118 | assert_eq!(*result.status(), StatusCode::from_u16(402).unwrap()); 119 | assert_eq!(result.body(), "Error"); 120 | } 121 | 122 | async fn test_status_bytes() { 123 | let api = new_pretend(); 124 | 125 | let result = api.get_bytes(200).await.unwrap(); 126 | assert_eq!(String::from_utf8_lossy(&result), "Hello World"); 127 | 128 | let result = api.get_bytes(402).await; 129 | assert_eq!(get_err_status(result), Some(402)); 130 | } 131 | 132 | async fn test_status_bytes_response() { 133 | let api = new_pretend(); 134 | 135 | let result = api.get_bytes_response(200).await.unwrap(); 136 | assert_eq!(*result.status(), StatusCode::from_u16(200).unwrap()); 137 | assert_eq!(String::from_utf8_lossy(&result.body()), "Hello World"); 138 | 139 | let result = api.get_bytes_response(402).await.unwrap(); 140 | assert_eq!(*result.status(), StatusCode::from_u16(402).unwrap()); 141 | assert_eq!(String::from_utf8_lossy(&result.body()), "Error"); 142 | } 143 | 144 | async fn test_status_json() { 145 | let api = new_pretend(); 146 | 147 | let expected = server::TestData { 148 | first: "Hello".to_string(), 149 | second: 123, 150 | }; 151 | let result = api.get_json(200).await.unwrap(); 152 | assert_eq!(result.value(), expected); 153 | 154 | let result = api.get_json(402).await; 155 | assert_eq!(get_err_status(result), Some(402)); 156 | } 157 | 158 | async fn test_status_json_response() { 159 | let api = new_pretend(); 160 | 161 | let expected = server::TestData { 162 | first: "Hello".to_string(), 163 | second: 123, 164 | }; 165 | let result = api.get_json_response(200).await.unwrap(); 166 | assert_eq!(*result.status(), StatusCode::from_u16(200).unwrap()); 167 | assert_eq!(result.into_body().value(), expected); 168 | 169 | let result = api.get_json_response(402).await; 170 | assert!(is_body_err(result)); 171 | } 172 | 173 | async fn test_status_json_result() { 174 | let api = new_pretend(); 175 | 176 | let expected = server::TestData { 177 | first: "Hello".to_string(), 178 | second: 123, 179 | }; 180 | let result = api.get_json_result(200).await.unwrap(); 181 | assert_eq!(result, JsonResult::Ok(expected)); 182 | 183 | let expected = server::ErrorData { 184 | message: "Error".to_string(), 185 | }; 186 | let result = api.get_json_result(402).await.unwrap(); 187 | assert_eq!(result, JsonResult::Err(expected)); 188 | } 189 | 190 | async fn test_status_json_result_response() { 191 | let api = new_pretend(); 192 | 193 | let expected = server::TestData { 194 | first: "Hello".to_string(), 195 | second: 123, 196 | }; 197 | let result = api.get_json_result_response(200).await.unwrap(); 198 | assert_eq!(*result.status(), StatusCode::from_u16(200).unwrap()); 199 | assert_eq!(result.into_body(), JsonResult::Ok(expected)); 200 | 201 | let expected = server::ErrorData { 202 | message: "Error".to_string(), 203 | }; 204 | let result = api.get_json_result_response(402).await.unwrap(); 205 | assert_eq!(*result.status(), StatusCode::from_u16(402).unwrap()); 206 | assert_eq!(result.into_body(), JsonResult::Err(expected)); 207 | } 208 | -------------------------------------------------------------------------------- /pretend/src/internal.rs: -------------------------------------------------------------------------------- 1 | #![doc(hidden)] 2 | 3 | //! Internal module used by the code generator 4 | 5 | use crate::client::{BlockingClient, Bytes, Client, LocalClient, Method}; 6 | use crate::interceptor::{InterceptRequest, Request}; 7 | use crate::resolver::ResolveUrl; 8 | use crate::{Error, HeaderMap, Json, JsonResult, Pretend, Response, Result}; 9 | use http::header::{HeaderName, CONTENT_TYPE}; 10 | use http::HeaderValue; 11 | use serde::de::DeserializeOwned; 12 | use serde::Serialize; 13 | use std::str::FromStr; 14 | use url::Url; 15 | 16 | /// Request body 17 | pub enum Body<'a, T> 18 | where 19 | T: Serialize, 20 | { 21 | /// No body 22 | None, 23 | /// Raw bytes 24 | Raw(Bytes), 25 | /// Form 26 | Form(&'a T), 27 | /// Json 28 | Json(&'a T), 29 | } 30 | 31 | /// Helper for pretend code generator 32 | pub struct MacroSupport<'p, C, R, I> 33 | where 34 | R: ResolveUrl, 35 | I: InterceptRequest, 36 | { 37 | pretend: &'p Pretend, 38 | } 39 | 40 | impl<'p, C, R, I> MacroSupport<'p, C, R, I> 41 | where 42 | R: ResolveUrl, 43 | I: InterceptRequest, 44 | { 45 | /// Constructor 46 | /// 47 | /// It wraps a `Pretend` instance 48 | pub fn new(pretend: &'p Pretend) -> Self { 49 | MacroSupport { pretend } 50 | } 51 | 52 | /// Create an url from the resolver and a path 53 | pub fn create_url(&self, path: &str) -> Result { 54 | let resolver = &self.pretend.resolver; 55 | let result = resolver.resolve_url(path); 56 | result.map_err(Error::request) 57 | } 58 | 59 | /// Execute a request 60 | /// 61 | /// Execute a request from request components. 62 | /// Serialize the body if needed. 63 | pub async fn request<'a, T>( 64 | &'a self, 65 | method: Method, 66 | url: Url, 67 | headers: HeaderMap, 68 | body: Body<'a, T>, 69 | ) -> Result> 70 | where 71 | C: Client, 72 | T: Serialize, 73 | { 74 | let client = &self.pretend.client; 75 | let (method, url, headers, body) = self.prepare_request(method, url, headers, body)?; 76 | client.execute(method, url, headers, body).await 77 | } 78 | 79 | /// Execute a request on a local client 80 | /// 81 | /// Execute a request from request components. 82 | /// Serialize the body if needed. 83 | pub async fn request_local<'a, T>( 84 | &'a self, 85 | method: Method, 86 | url: Url, 87 | headers: HeaderMap, 88 | body: Body<'a, T>, 89 | ) -> Result> 90 | where 91 | C: LocalClient, 92 | T: Serialize, 93 | { 94 | let client = &self.pretend.client; 95 | let (method, url, headers, body) = self.prepare_request(method, url, headers, body)?; 96 | client.execute(method, url, headers, body).await 97 | } 98 | 99 | /// Execute a blocking request 100 | /// 101 | /// Execute a request from request components. 102 | /// Serialize the body if needed. 103 | pub fn request_blocking<'a, T>( 104 | &'a self, 105 | method: Method, 106 | url: Url, 107 | headers: HeaderMap, 108 | body: Body<'a, T>, 109 | ) -> Result> 110 | where 111 | C: BlockingClient, 112 | T: Serialize, 113 | { 114 | let client = &self.pretend.client; 115 | let (method, url, headers, body) = self.prepare_request(method, url, headers, body)?; 116 | client.execute(method, url, headers, body) 117 | } 118 | 119 | fn prepare_request<'a, T>( 120 | &'a self, 121 | method: Method, 122 | url: Url, 123 | mut headers: HeaderMap, 124 | body: Body<'a, T>, 125 | ) -> Result<(Method, Url, HeaderMap, Option)> 126 | where 127 | T: Serialize, 128 | { 129 | let (headers, body) = match body { 130 | Body::None => (headers, None), 131 | Body::Raw(raw) => (headers, Some(raw)), 132 | Body::Form(form) => { 133 | let content_type = HeaderValue::from_static("application/x-www-form-urlencoded"); 134 | headers.insert(CONTENT_TYPE, content_type); 135 | 136 | let encoded = serde_urlencoded::to_string(form); 137 | let encoded = encoded.map_err(Error::request)?; 138 | let body = Some(Bytes::from(encoded)); 139 | 140 | (headers, body) 141 | } 142 | Body::Json(json) => { 143 | let content_type = HeaderValue::from_static("application/json"); 144 | headers.insert(CONTENT_TYPE, content_type); 145 | 146 | let encoded = serde_json::to_vec(json); 147 | let encoded = encoded.map_err(Error::request)?; 148 | let body = Some(Bytes::from(encoded)); 149 | 150 | (headers, body) 151 | } 152 | }; 153 | 154 | let request = Request::new(method, url, headers); 155 | let request = self.pretend.interceptor.intercept(request)?; 156 | Ok((request.method, request.url, request.headers, body)) 157 | } 158 | } 159 | 160 | /// Update the query component of an Url 161 | pub fn build_query(mut url: Url, query: &T) -> Result 162 | where 163 | T: Serialize, 164 | { 165 | { 166 | let mut pairs = url.query_pairs_mut(); 167 | let serializer = serde_urlencoded::Serializer::new(&mut pairs); 168 | let result = query.serialize(serializer); 169 | result.map_err(Error::request)?; 170 | } 171 | Ok(url) 172 | } 173 | 174 | /// Append a component to a header 175 | pub fn build_header(headers: &mut HeaderMap, name: &str, value: &str) -> Result<()> { 176 | let name = HeaderName::from_str(name).map_err(Error::request)?; 177 | let value = HeaderValue::from_str(value).map_err(Error::request)?; 178 | headers.append(name, value); 179 | Ok(()) 180 | } 181 | 182 | /// Trait to convert into a response 183 | /// 184 | /// This trait is responsible to convert a raw body 185 | /// into a response. Implementations of these traits 186 | /// handle raw bytes, strings, JSON and responses 187 | pub trait IntoResponse { 188 | fn into_response(self) -> Result; 189 | } 190 | 191 | impl IntoResponse<()> for Response { 192 | fn into_response(self) -> Result<()> { 193 | if self.status.is_success() { 194 | Ok(()) 195 | } else { 196 | Err(Error::Status(self.status)) 197 | } 198 | } 199 | } 200 | 201 | impl IntoResponse> for Response { 202 | fn into_response(self) -> Result> { 203 | let (status, headers, _) = self.into_parts(); 204 | Ok(Response::new(status, headers, ())) 205 | } 206 | } 207 | 208 | impl IntoResponse for Response { 209 | fn into_response(self) -> Result { 210 | if self.status.is_success() { 211 | Ok(parse_string_body(&self)) 212 | } else { 213 | Err(Error::Status(self.status)) 214 | } 215 | } 216 | } 217 | 218 | impl IntoResponse> for Response { 219 | fn into_response(self) -> Result> { 220 | let body = parse_string_body(&self); 221 | Ok(Response::new(self.status, self.headers, body)) 222 | } 223 | } 224 | 225 | fn parse_string_body(response: &Response) -> String { 226 | // Taken from reqwest 227 | let content_type = response.headers.get(CONTENT_TYPE); 228 | let content_type = content_type 229 | .and_then(|value| value.to_str().ok()) 230 | .and_then(|value| value.parse::().ok()); 231 | let encoding_name = content_type 232 | .as_ref() 233 | .and_then(|mime| mime.get_param("charset").map(|charset| charset.as_str())) 234 | .unwrap_or("utf-8"); 235 | 236 | let encoding = encoding_rs::Encoding::for_label(encoding_name.as_bytes()); 237 | let encoding = encoding.unwrap_or(encoding_rs::UTF_8); 238 | 239 | let (text, _, _) = encoding.decode(&response.body); 240 | text.to_string() 241 | } 242 | 243 | impl IntoResponse> for Response { 244 | fn into_response(self) -> Result> { 245 | if self.status.is_success() { 246 | Ok(self.body.to_vec()) 247 | } else { 248 | Err(Error::Status(self.status)) 249 | } 250 | } 251 | } 252 | 253 | impl IntoResponse>> for Response { 254 | fn into_response(self) -> Result>> { 255 | Ok(Response::new(self.status, self.headers, self.body.to_vec())) 256 | } 257 | } 258 | 259 | impl IntoResponse> for Response 260 | where 261 | T: DeserializeOwned, 262 | { 263 | fn into_response(self) -> Result> { 264 | if self.status.is_success() { 265 | let value = parse_json(self.body)?; 266 | Ok(Json { value }) 267 | } else { 268 | Err(Error::Status(self.status)) 269 | } 270 | } 271 | } 272 | 273 | impl IntoResponse>> for Response 274 | where 275 | T: DeserializeOwned, 276 | { 277 | fn into_response(self) -> Result>> { 278 | let value = parse_json(self.body)?; 279 | let body = Json { value }; 280 | Ok(Response::new(self.status, self.headers, body)) 281 | } 282 | } 283 | 284 | impl IntoResponse> for Response 285 | where 286 | T: DeserializeOwned, 287 | E: DeserializeOwned, 288 | { 289 | fn into_response(self) -> Result> { 290 | if self.status.is_success() { 291 | let value = parse_json(self.body)?; 292 | Ok(JsonResult::Ok(value)) 293 | } else { 294 | let value = parse_json(self.body)?; 295 | Ok(JsonResult::Err(value)) 296 | } 297 | } 298 | } 299 | 300 | impl IntoResponse>> for Response 301 | where 302 | T: DeserializeOwned, 303 | E: DeserializeOwned, 304 | { 305 | fn into_response(self) -> Result>> { 306 | if self.status.is_success() { 307 | let value = parse_json(self.body)?; 308 | Ok(Response::new( 309 | self.status, 310 | self.headers, 311 | JsonResult::Ok(value), 312 | )) 313 | } else { 314 | let value = parse_json(self.body)?; 315 | Ok(Response::new( 316 | self.status, 317 | self.headers, 318 | JsonResult::Err(value), 319 | )) 320 | } 321 | } 322 | } 323 | 324 | fn parse_json(body: Bytes) -> Result 325 | where 326 | T: DeserializeOwned, 327 | { 328 | serde_json::from_slice(body.as_ref()).map_err(Error::body) 329 | } 330 | -------------------------------------------------------------------------------- /pretend/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! `pretend` HTTP client 2 | //! 3 | //! `pretend` is a modular, [Feign]-inspired, HTTP client based on macros. It's goal is to decouple 4 | //! the definition of a REST API from it's implementation. 5 | //! 6 | //! Some features: 7 | //! - Declarative 8 | //! - Support Asynchronous and blocking requests 9 | //! - HTTP client agnostic 10 | //! - JSON support thanks to serde 11 | //! 12 | //! [Feign]: https://github.com/OpenFeign/feign 13 | //! 14 | //! # Getting started 15 | //! 16 | //! A REST API is described by annotating a trait: 17 | //! 18 | //! ```rust 19 | //! use pretend::{pretend, Result}; 20 | //! 21 | //! #[pretend] 22 | //! trait HttpBin { 23 | //! #[request(method = "POST", path = "/anything")] 24 | //! async fn post_anything(&self, body: &'static str) -> Result; 25 | //! } 26 | //! ``` 27 | //! 28 | //! Under the hood, `pretend` will implement this trait for `Pretend`. An instance of this 29 | //! struct can be constructed by passing a client implementation, and the REST API's base url. In 30 | //! the following example, we are using the [`reqwest`] based client. 31 | //! 32 | //! [`reqwest`]: https://crates.io/crates/pretend-reqwest 33 | //! 34 | //! ```rust 35 | //! use pretend::{Pretend, Url}; 36 | //! use pretend_reqwest::Client; 37 | //! # use pretend::{pretend, Result}; 38 | //! # #[pretend] 39 | //! # trait HttpBin { 40 | //! # #[request(method = "POST", path = "/anything")] 41 | //! # async fn post_anything(&self, body: &'static str) -> Result; 42 | //! # } 43 | //! 44 | //! # #[tokio::main] 45 | //! # async fn main() { 46 | //! let client = Client::default(); 47 | //! let url = Url::parse("https://httpbin.org").unwrap(); 48 | //! let pretend = Pretend::for_client(client).with_url(url); 49 | //! let response = pretend.post_anything("hello").await.unwrap(); 50 | //! assert!(response.contains("hello")); 51 | //! # } 52 | //! ``` 53 | //! 54 | //! # Sending headers, query parameters and bodies 55 | //! 56 | //! Headers are provided as attributes using `header`. 57 | //! 58 | //! ```rust 59 | //! use pretend::{pretend, Result}; 60 | //! 61 | //! #[pretend] 62 | //! trait HttpBin { 63 | //! #[request(method = "GET", path = "/get")] 64 | //! #[header(name = "X-Test-Header-1", value = "abc")] 65 | //! #[header(name = "X-Test-Header-2", value = "other")] 66 | //! async fn get_with_headers(&self, value: i32, custom: &str) -> Result<()>; 67 | //! } 68 | //! ``` 69 | //! 70 | //! Query parameters and bodies are provided as method parameters. Body type is guessed based on 71 | //! the parameter name: 72 | //! 73 | //! - Parameter `body` will be sent as raw bytes. 74 | //! - Parameter `form` will be serialized as form-encoded using `serde`. 75 | //! - Parameter `json` will be serialized as JSON using `serde`. 76 | //! 77 | //! Query parameter is passed with the `query` parameter. It is also serialized using `serde`. 78 | //! 79 | //! ```rust 80 | //! use pretend::{pretend, Json, Result}; 81 | //! use serde::Serialize; 82 | //! 83 | //! #[derive(Serialize)] 84 | //! struct Data { 85 | //! value: i32, 86 | //! } 87 | //! 88 | //! #[pretend] 89 | //! trait HttpBin { 90 | //! #[request(method = "POST", path = "/anything")] 91 | //! async fn post_bytes(&self, body: Vec) -> Result<()>; 92 | //! 93 | //! #[request(method = "POST", path = "/anything")] 94 | //! async fn post_string(&self, body: &'static str) -> Result<()>; 95 | //! 96 | //! #[request(method = "POST", path = "/anything")] 97 | //! async fn post_with_query_params(&self, query: &Data) -> Result<()>; 98 | //! 99 | //! #[request(method = "POST", path = "/anything")] 100 | //! async fn post_json(&self, json: &Data) -> Result<()>; 101 | //! } 102 | //! ``` 103 | //! 104 | //! # Handling responses 105 | //! 106 | //! `pretend` support a wide range of response types, based on the return type of the method. 107 | //! The body can be returned as a `Vec`, a string or as JSON by using the [`Json`] wrapper 108 | //! type. The unit type `()` can also be used if the body should be discarded. 109 | //! 110 | //! [`JsonResult`] is also offered as a convenience type. It will deserialize into a value type 111 | //! or an error type depending on the HTTP status code. 112 | //! 113 | //! When retrieving body alone, an HTTP error will cause the method to return an error. It is 114 | //! possible to prevent the method to fail and access the HTTP status code by wrapping these 115 | //! types inside a [`Response`]. This also allows accessing response headers. 116 | //! 117 | //! ```rust 118 | //! use pretend::{pretend, Json, JsonResult, Response, Result}; 119 | //! use serde::Deserialize; 120 | //! 121 | //! #[derive(Deserialize)] 122 | //! struct Data { 123 | //! value: i32, 124 | //! } 125 | //! 126 | //! #[derive(Deserialize)] 127 | //! struct Error { 128 | //! error: String, 129 | //! } 130 | //! 131 | //! #[pretend] 132 | //! trait HttpBin { 133 | //! #[request(method = "POST", path = "/anything")] 134 | //! async fn get_bytes(&self) -> Result>; 135 | //! 136 | //! #[request(method = "POST", path = "/anything")] 137 | //! async fn get_string(&self) -> Result; 138 | //! 139 | //! #[request(method = "POST", path = "/anything")] 140 | //! async fn get_json(&self) -> Result>; 141 | //! 142 | //! #[request(method = "POST", path = "/anything")] 143 | //! async fn get_json_result(&self) -> Result>; 144 | //! 145 | //! #[request(method = "POST", path = "/anything")] 146 | //! async fn get_status(&self) -> Result>; 147 | //! } 148 | //! ``` 149 | //! 150 | //! # Templating 151 | //! 152 | //! Request paths and headers support templating. A value between braces will be replaced by 153 | //! a parameter with the same name. The replacement is done with `format!`, meaning that 154 | //! any type that implement `Display` is supported. 155 | //! 156 | //! ```rust 157 | //! use pretend::{pretend, Json, Pretend, Result, Url}; 158 | //! use pretend_reqwest::Client; 159 | //! use serde::Deserialize; 160 | //! use std::collections::HashMap; 161 | //! 162 | //! #[derive(Deserialize)] 163 | //! struct Data { 164 | //! url: String, 165 | //! headers: HashMap, 166 | //! } 167 | //! 168 | //! #[pretend] 169 | //! trait HttpBin { 170 | //! #[request(method = "POST", path = "/{path}")] 171 | //! #[header(name = "X-{header}", value = "{value}$")] 172 | //! async fn get(&self, path: &str, header: &str, value: i32) -> Result>; 173 | //! } 174 | //! 175 | //! # #[tokio::main] 176 | //! # async fn main() { 177 | //! let client = Client::default(); 178 | //! let url = Url::parse("https://httpbin.org").unwrap(); 179 | //! let pretend = Pretend::for_client(client).with_url(url); 180 | //! let response = pretend.get("anything", "My-Header", 123).await.unwrap(); 181 | //! let data = response.value(); 182 | //! assert_eq!(data.url, "https://httpbin.org/anything"); 183 | //! assert_eq!(*data.headers.get("X-My-Header").unwrap(), "123$".to_string()); 184 | //! # } 185 | //! ``` 186 | //! 187 | //! # URL resolvers 188 | //! 189 | //! `pretend` uses URL resolvers to resolve a full URL from the path in `request`. By default 190 | //! the URL resolver will simply append the path to a base URL. More advanced resolvers can 191 | //! be implemented with the [resolver] module. 192 | //! 193 | //! # Request interceptors 194 | //! 195 | //! `pretend` uses request interceptors to customize auto-generated requests. They can be useful 196 | //! when dealing with authentication. They can be implemented with the [interceptor] module. 197 | //! 198 | //! ```rust 199 | //! use pretend::http::header::AUTHORIZATION; 200 | //! use pretend::http::HeaderValue; 201 | //! use pretend::interceptor::{InterceptRequest, Request}; 202 | //! use pretend::{pretend, Error, Json, Pretend, Result, Url}; 203 | //! use pretend_reqwest::Client; 204 | //! use serde::Deserialize; 205 | //! use std::collections::HashMap; 206 | //! 207 | //! #[derive(Deserialize)] 208 | //! struct Data { 209 | //! url: String, 210 | //! headers: HashMap, 211 | //! } 212 | //! 213 | //! #[pretend] 214 | //! trait HttpBin { 215 | //! #[request(method = "GET", path = "/get")] 216 | //! async fn get(&self) -> Result>; 217 | //! } 218 | //! 219 | //! struct AuthInterceptor { 220 | //! auth: String, 221 | //! } 222 | //! 223 | //! impl AuthInterceptor { 224 | //! fn new(auth: String) -> Self { 225 | //! AuthInterceptor { auth } 226 | //! } 227 | //! } 228 | //! 229 | //! impl InterceptRequest for AuthInterceptor { 230 | //! fn intercept(&self, mut request: Request) -> Result { 231 | //! // Create the header, reporting failure if the header is invalid 232 | //! let header = format!("Bearer {}", self.auth); 233 | //! let header = HeaderValue::from_str(&header).map_err(|err| Error::Request(Box::new(err)))?; 234 | //! 235 | //! // Set the authorization header in the request 236 | //! request.headers.append(AUTHORIZATION, header); 237 | //! Ok(request) 238 | //! } 239 | //! } 240 | //! # #[tokio::main] 241 | //! # async fn main() { 242 | //! let client = Client::default(); 243 | //! let url = Url::parse("https://httpbin.org").unwrap(); 244 | //! let auth_interceptor = AuthInterceptor::new("test".to_string()); 245 | //! let pretend = Pretend::for_client(client).with_url(url).with_request_interceptor(auth_interceptor); 246 | //! let response = pretend.get().await.unwrap(); 247 | //! let data = response.value(); 248 | //! assert_eq!(*data.headers.get("Authorization").unwrap(), "Bearer test".to_string()); 249 | //! # } 250 | //! ``` 251 | //! 252 | //! # Examples 253 | //! 254 | //! More examples are available in the [examples folder]. 255 | //! 256 | //! [examples folder]: https://github.com/SfietKonstantin/pretend/tree/main/pretend/examples 257 | //! 258 | //! # Blocking requests 259 | //! 260 | //! When all methods in the `pretend`-annotated trait are async, `pretend` will generate 261 | //! an async implementation. To generate a blocking implementation, simply remove the `async` 262 | //! keyword. 263 | //! 264 | //! Blocking implementations needs a blocking client implementation to be used. In the following 265 | //! example, we are using one provided by [`reqwest`] 266 | //! 267 | //! ```rust 268 | //! use pretend::{pretend, Pretend, Result, Url}; 269 | //! use pretend_reqwest::BlockingClient; 270 | //! 271 | //! #[pretend] 272 | //! trait HttpBin { 273 | //! #[request(method = "POST", path = "/anything")] 274 | //! fn post_anything(&self, body: &'static str) -> Result; 275 | //! } 276 | //! 277 | //! # fn main() { 278 | //! let client = BlockingClient::default(); 279 | //! let url = Url::parse("https://httpbin.org").unwrap(); 280 | //! let pretend = Pretend::for_client(client).with_url(url); 281 | //! let response = pretend.post_anything("hello").unwrap(); 282 | //! assert!(response.contains("hello")); 283 | //! # } 284 | //! ``` 285 | //! 286 | //! [`reqwest`]: https://crates.io/crates/pretend-reqwest 287 | //! 288 | //! # Non-Send implementation 289 | //! 290 | //! Today, Rust does not support futures in traits. `pretend` uses `async_trait` to workaround 291 | //! that limitation. By default, `async_trait` adds the `Send` bound to futures. This implies 292 | //! that `Pretend` itself is `Send` and `Sync`, and implies that the client implementation it uses 293 | //! is also `Send` and `Sync`. 294 | //! 295 | //! However, some clients are not thread-safe, and cannot be shared between threads. To use 296 | //! these clients with `Pretend`, you have to opt-out from the `Send` constraint on returned 297 | //! futures by using `#[pretend(?Send)]`. This is similar to what is done in [`async_trait`]. 298 | //! 299 | //! [`async_trait`]: https://docs.rs/async-trait/latest/async_trait/ 300 | //! 301 | //! Clients implementations that are not thread-safe are usually called "local clients". 302 | //! 303 | //! # Non-Send errors 304 | //! 305 | //! `pretend` boxes errors returned by the client in [`Error`]. By default, it requires the error 306 | //! to be `Send + Sync`. For some clients, especially local ones, this bound cannot be guaranteed. 307 | //! 308 | //! `pretend` offers the feature `local-error` as an escape latch. When enabled, this feature will 309 | //! drop the `Send + Sync` bound on boxed errors. This feature is enabled by default for 310 | //! `pretend-awc`, a local client that returns non-Send errors. 311 | //! 312 | //! # Available client implementations 313 | //! 314 | //! `pretend` can be used with the following HTTP clients 315 | //! 316 | //! - [`reqwest`](https://crates.io/crates/pretend-reqwest) (async and blocking) 317 | //! - [`isahc`](https://crates.io/crates/pretend-isahc) (async) 318 | //! - [`awc`](https://crates.io/crates/pretend-awc) (local async) 319 | //! - [`ureq`](https://crates.io/crates/pretend-ureq) (blocking) 320 | //! 321 | //! These client implementations depends on the latest major release of each HTTP client at 322 | //! time of the release. The `default` feature for each of the HTTP client crate is also mapped 323 | //! to the `pretend-*` crate. To enable HTTP client features, you should add it as a dependency 324 | //! and enable them here. If needed, you can play with the `default-features` option on the 325 | //! `pretend-*` crate. 326 | //! 327 | //! The following snippet will enable `reqwest` default features 328 | //! 329 | //! ```toml 330 | //! [dependencies] 331 | //! pretend-reqwest = "0.2.2" 332 | //! ``` 333 | //! 334 | //! In the following snippet, no feature of `reqwest` will be enabled 335 | //! 336 | //! ```toml 337 | //! [dependencies] 338 | //! pretend-reqwest = { version = "0.2.2", default-features = false } 339 | //! ``` 340 | //! 341 | //! To use `reqwest` with rustls instead of the native-tls, you can do the following: 342 | //! 343 | //! ```toml 344 | //! [dependencies] 345 | //! pretend-reqwest = { version = "0.2.2", default-features = false } 346 | //! reqwest = { version = "*", default-features = false, features = ["rustls-tls"] } 347 | //! ``` 348 | //! 349 | //! # Implementing a `pretend` HTTP client 350 | //! 351 | //! `pretend` clients wraps HTTP clients from other crates. They allow [`Pretend`] to execute 352 | //! HTTP requests. See the [client] module level documentation for more information about 353 | //! how to implement a client. 354 | //! 355 | //! # MSRV 356 | //! 357 | //! MSRV for the `pretend` ecosystem is Rust **1.44**. 358 | //! 359 | //! # The future 360 | //! 361 | //! Here is a quick roadmap 362 | //! 363 | //! - Introduce more attributes to mark method parameters (body, json, params) 364 | 365 | #![warn(missing_docs)] 366 | #![forbid(unsafe_code)] 367 | 368 | pub mod client; 369 | pub mod interceptor; 370 | pub mod internal; 371 | pub mod resolver; 372 | 373 | mod errors; 374 | 375 | pub use self::errors::{Error, Result}; 376 | pub use http; 377 | pub use http::{HeaderMap, StatusCode}; 378 | pub use pretend_codegen::pretend; 379 | pub use serde; 380 | pub use url; 381 | pub use url::Url; 382 | 383 | use crate::interceptor::{InterceptRequest, NoopRequestInterceptor}; 384 | use crate::resolver::{InvalidUrlResolver, ResolveUrl, UrlResolver}; 385 | use serde::de::DeserializeOwned; 386 | use std::ops::{Deref, DerefMut}; 387 | 388 | /// Response type 389 | #[derive(Clone, Debug, Eq, PartialEq)] 390 | pub struct Response { 391 | status: StatusCode, 392 | headers: HeaderMap, 393 | body: T, 394 | } 395 | 396 | impl Response { 397 | /// Constructor 398 | pub fn new(status: StatusCode, headers: HeaderMap, body: T) -> Self { 399 | Response { 400 | status, 401 | headers, 402 | body, 403 | } 404 | } 405 | 406 | /// HTTP status 407 | pub fn status(&self) -> &StatusCode { 408 | &self.status 409 | } 410 | 411 | /// Response headers 412 | pub fn headers(&self) -> &HeaderMap { 413 | &self.headers 414 | } 415 | 416 | /// Response body 417 | pub fn body(&self) -> &T { 418 | &self.body 419 | } 420 | 421 | /// Consume this instance to return the body 422 | pub fn into_body(self) -> T { 423 | self.body 424 | } 425 | 426 | /// Consume this instance to return the status, headers and body 427 | pub fn into_parts(self) -> (StatusCode, HeaderMap, T) { 428 | (self.status, self.headers, self.body) 429 | } 430 | } 431 | 432 | /// The pretend HTTP client 433 | /// 434 | /// This struct is the entry point for `pretend` clients. It can be constructed with 435 | /// an HTTP client implementation, and `pretend` annotated traits will automatically 436 | /// be implemented by this struct. 437 | /// 438 | /// See crate level documentation for more information 439 | #[derive(Clone, Debug)] 440 | pub struct Pretend 441 | where 442 | R: ResolveUrl, 443 | I: InterceptRequest, 444 | { 445 | client: C, 446 | resolver: R, 447 | interceptor: I, 448 | } 449 | 450 | impl Pretend 451 | where 452 | R: ResolveUrl, 453 | I: InterceptRequest, 454 | { 455 | /// Constructor 456 | /// 457 | /// This constructor takes a client implementation, an URL resolver and 458 | /// an interceptor. Prefer using [`Pretend::for_client`] and [`Pretend::with_url`]. 459 | pub fn new(client: C, resolver: R, interceptor: I) -> Pretend { 460 | Pretend { 461 | client, 462 | resolver, 463 | interceptor, 464 | } 465 | } 466 | 467 | /// Set the base URL 468 | /// 469 | /// Set the base URL for this client. 470 | pub fn with_url(self, url: Url) -> Pretend { 471 | self.with_url_resolver(UrlResolver::new(url)) 472 | } 473 | 474 | /// Set the request interceptor 475 | /// 476 | /// Set the request interceptor for this client. 477 | pub fn with_request_interceptor(self, interceptor: II) -> Pretend 478 | where 479 | II: InterceptRequest, 480 | { 481 | Pretend::new(self.client, self.resolver, interceptor) 482 | } 483 | 484 | /// Set the URL resolver 485 | /// 486 | /// Set the URL resolver for this client. 487 | pub fn with_url_resolver(self, resolver: RR) -> Pretend 488 | where 489 | RR: ResolveUrl, 490 | { 491 | Pretend::new(self.client, resolver, self.interceptor) 492 | } 493 | } 494 | 495 | impl Pretend { 496 | /// Constructor 497 | /// 498 | /// This constructor takes a client implementation and 499 | /// return an incomplete `Pretend` client. Use [`Pretend::with_url`] to 500 | /// set the base URL. 501 | pub fn for_client(client: C) -> Pretend { 502 | Pretend::new(client, InvalidUrlResolver, NoopRequestInterceptor) 503 | } 504 | } 505 | 506 | /// JSON body 507 | /// 508 | /// This wrapper type indicates that a method should return 509 | /// a JSON-serialized body. 510 | #[derive(Debug, Clone, Eq, PartialEq, Hash)] 511 | pub struct Json 512 | where 513 | T: DeserializeOwned, 514 | { 515 | value: T, 516 | } 517 | 518 | impl Json 519 | where 520 | T: DeserializeOwned, 521 | { 522 | /// Deserialized value 523 | pub fn value(self) -> T { 524 | self.value 525 | } 526 | } 527 | 528 | impl AsRef for Json 529 | where 530 | T: DeserializeOwned, 531 | { 532 | fn as_ref(&self) -> &T { 533 | &self.value 534 | } 535 | } 536 | 537 | impl AsMut for Json 538 | where 539 | T: DeserializeOwned, 540 | { 541 | fn as_mut(&mut self) -> &mut T { 542 | &mut self.value 543 | } 544 | } 545 | 546 | impl Deref for Json 547 | where 548 | T: DeserializeOwned, 549 | { 550 | type Target = T; 551 | 552 | fn deref(&self) -> &Self::Target { 553 | &self.value 554 | } 555 | } 556 | 557 | impl DerefMut for Json 558 | where 559 | T: DeserializeOwned, 560 | { 561 | fn deref_mut(&mut self) -> &mut Self::Target { 562 | &mut self.value 563 | } 564 | } 565 | 566 | /// JSON result 567 | /// 568 | /// This wrapper type indicate that a method should return 569 | /// JSON-serialized bodies. 570 | /// 571 | /// When the HTTP request is successful, the `Ok` variant will 572 | /// be returned, and when the HTTP request has failed, the 573 | /// `Err` variant will be returned. 574 | #[derive(Debug, Clone, Eq, PartialEq, Hash)] 575 | pub enum JsonResult 576 | where 577 | T: DeserializeOwned, 578 | E: DeserializeOwned, 579 | { 580 | /// Successful value 581 | Ok(T), 582 | /// Error value 583 | Err(E), 584 | } 585 | -------------------------------------------------------------------------------- /tests/default-features-build/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.20" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anyhow" 16 | version = "1.0.69" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" 19 | 20 | [[package]] 21 | name = "async-channel" 22 | version = "1.8.0" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" 25 | dependencies = [ 26 | "concurrent-queue", 27 | "event-listener", 28 | "futures-core", 29 | ] 30 | 31 | [[package]] 32 | name = "async-trait" 33 | version = "0.1.64" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" 36 | dependencies = [ 37 | "proc-macro2", 38 | "quote", 39 | "syn", 40 | ] 41 | 42 | [[package]] 43 | name = "autocfg" 44 | version = "1.1.0" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 47 | 48 | [[package]] 49 | name = "base64" 50 | version = "0.13.1" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" 53 | 54 | [[package]] 55 | name = "base64" 56 | version = "0.21.0" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" 59 | 60 | [[package]] 61 | name = "basic-toml" 62 | version = "0.1.1" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "2e819b667739967cd44d308b8c7b71305d8bb0729ac44a248aa08f33d01950b4" 65 | dependencies = [ 66 | "serde", 67 | ] 68 | 69 | [[package]] 70 | name = "bumpalo" 71 | version = "3.12.0" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" 74 | 75 | [[package]] 76 | name = "bytes" 77 | version = "1.4.0" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" 80 | 81 | [[package]] 82 | name = "castaway" 83 | version = "0.1.2" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" 86 | 87 | [[package]] 88 | name = "cc" 89 | version = "1.0.79" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" 92 | 93 | [[package]] 94 | name = "cfg-if" 95 | version = "1.0.0" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 98 | 99 | [[package]] 100 | name = "chunked_transfer" 101 | version = "1.4.1" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "cca491388666e04d7248af3f60f0c40cfb0991c72205595d7c396e3510207d1a" 104 | 105 | [[package]] 106 | name = "concurrent-queue" 107 | version = "2.1.0" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "c278839b831783b70278b14df4d45e1beb1aad306c07bb796637de9a0e323e8e" 110 | dependencies = [ 111 | "crossbeam-utils", 112 | ] 113 | 114 | [[package]] 115 | name = "crossbeam-utils" 116 | version = "0.8.14" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" 119 | dependencies = [ 120 | "cfg-if", 121 | ] 122 | 123 | [[package]] 124 | name = "curl" 125 | version = "0.4.44" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22" 128 | dependencies = [ 129 | "curl-sys", 130 | "libc", 131 | "openssl-probe", 132 | "openssl-sys", 133 | "schannel", 134 | "socket2", 135 | "winapi", 136 | ] 137 | 138 | [[package]] 139 | name = "curl-sys" 140 | version = "0.4.59+curl-7.86.0" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "6cfce34829f448b08f55b7db6d0009e23e2e86a34e8c2b366269bf5799b4a407" 143 | dependencies = [ 144 | "cc", 145 | "libc", 146 | "libz-sys", 147 | "openssl-sys", 148 | "pkg-config", 149 | "vcpkg", 150 | "winapi", 151 | ] 152 | 153 | [[package]] 154 | name = "encoding_rs" 155 | version = "0.8.32" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" 158 | dependencies = [ 159 | "cfg-if", 160 | ] 161 | 162 | [[package]] 163 | name = "event-listener" 164 | version = "2.5.3" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" 167 | 168 | [[package]] 169 | name = "fastrand" 170 | version = "1.8.0" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" 173 | dependencies = [ 174 | "instant", 175 | ] 176 | 177 | [[package]] 178 | name = "fnv" 179 | version = "1.0.7" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 182 | 183 | [[package]] 184 | name = "form_urlencoded" 185 | version = "1.1.0" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" 188 | dependencies = [ 189 | "percent-encoding", 190 | ] 191 | 192 | [[package]] 193 | name = "futures-channel" 194 | version = "0.3.26" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" 197 | dependencies = [ 198 | "futures-core", 199 | ] 200 | 201 | [[package]] 202 | name = "futures-core" 203 | version = "0.3.26" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" 206 | 207 | [[package]] 208 | name = "futures-io" 209 | version = "0.3.26" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" 212 | 213 | [[package]] 214 | name = "futures-lite" 215 | version = "1.12.0" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" 218 | dependencies = [ 219 | "fastrand", 220 | "futures-core", 221 | "futures-io", 222 | "memchr", 223 | "parking", 224 | "pin-project-lite", 225 | "waker-fn", 226 | ] 227 | 228 | [[package]] 229 | name = "futures-sink" 230 | version = "0.3.26" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" 233 | 234 | [[package]] 235 | name = "futures-task" 236 | version = "0.3.26" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" 239 | 240 | [[package]] 241 | name = "futures-util" 242 | version = "0.3.26" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" 245 | dependencies = [ 246 | "futures-core", 247 | "futures-task", 248 | "pin-project-lite", 249 | "pin-utils", 250 | ] 251 | 252 | [[package]] 253 | name = "glob" 254 | version = "0.3.1" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" 257 | 258 | [[package]] 259 | name = "h2" 260 | version = "0.3.15" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" 263 | dependencies = [ 264 | "bytes", 265 | "fnv", 266 | "futures-core", 267 | "futures-sink", 268 | "futures-util", 269 | "http", 270 | "indexmap", 271 | "slab", 272 | "tokio", 273 | "tokio-util", 274 | "tracing", 275 | ] 276 | 277 | [[package]] 278 | name = "hashbrown" 279 | version = "0.12.3" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 282 | 283 | [[package]] 284 | name = "hermit-abi" 285 | version = "0.2.6" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" 288 | dependencies = [ 289 | "libc", 290 | ] 291 | 292 | [[package]] 293 | name = "http" 294 | version = "0.2.8" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" 297 | dependencies = [ 298 | "bytes", 299 | "fnv", 300 | "itoa", 301 | ] 302 | 303 | [[package]] 304 | name = "http-body" 305 | version = "0.4.5" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" 308 | dependencies = [ 309 | "bytes", 310 | "http", 311 | "pin-project-lite", 312 | ] 313 | 314 | [[package]] 315 | name = "httparse" 316 | version = "1.8.0" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" 319 | 320 | [[package]] 321 | name = "httpdate" 322 | version = "1.0.2" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" 325 | 326 | [[package]] 327 | name = "hyper" 328 | version = "0.14.24" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c" 331 | dependencies = [ 332 | "bytes", 333 | "futures-channel", 334 | "futures-core", 335 | "futures-util", 336 | "h2", 337 | "http", 338 | "http-body", 339 | "httparse", 340 | "httpdate", 341 | "itoa", 342 | "pin-project-lite", 343 | "socket2", 344 | "tokio", 345 | "tower-service", 346 | "tracing", 347 | "want", 348 | ] 349 | 350 | [[package]] 351 | name = "idna" 352 | version = "0.3.0" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" 355 | dependencies = [ 356 | "unicode-bidi", 357 | "unicode-normalization", 358 | ] 359 | 360 | [[package]] 361 | name = "indexmap" 362 | version = "1.9.2" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" 365 | dependencies = [ 366 | "autocfg", 367 | "hashbrown", 368 | ] 369 | 370 | [[package]] 371 | name = "instant" 372 | version = "0.1.12" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 375 | dependencies = [ 376 | "cfg-if", 377 | ] 378 | 379 | [[package]] 380 | name = "ipnet" 381 | version = "2.7.1" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" 384 | 385 | [[package]] 386 | name = "isahc" 387 | version = "1.7.2" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9" 390 | dependencies = [ 391 | "async-channel", 392 | "castaway", 393 | "crossbeam-utils", 394 | "curl", 395 | "curl-sys", 396 | "event-listener", 397 | "futures-lite", 398 | "http", 399 | "log", 400 | "once_cell", 401 | "polling", 402 | "slab", 403 | "sluice", 404 | "tracing", 405 | "tracing-futures", 406 | "url", 407 | "waker-fn", 408 | ] 409 | 410 | [[package]] 411 | name = "itoa" 412 | version = "1.0.5" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" 415 | 416 | [[package]] 417 | name = "js-sys" 418 | version = "0.3.61" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" 421 | dependencies = [ 422 | "wasm-bindgen", 423 | ] 424 | 425 | [[package]] 426 | name = "lazy_static" 427 | version = "1.4.0" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 430 | 431 | [[package]] 432 | name = "libc" 433 | version = "0.2.139" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" 436 | 437 | [[package]] 438 | name = "libz-sys" 439 | version = "1.1.8" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" 442 | dependencies = [ 443 | "cc", 444 | "libc", 445 | "pkg-config", 446 | "vcpkg", 447 | ] 448 | 449 | [[package]] 450 | name = "log" 451 | version = "0.4.17" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 454 | dependencies = [ 455 | "cfg-if", 456 | ] 457 | 458 | [[package]] 459 | name = "memchr" 460 | version = "2.5.0" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 463 | 464 | [[package]] 465 | name = "mime" 466 | version = "0.3.16" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" 469 | 470 | [[package]] 471 | name = "mio" 472 | version = "0.8.5" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" 475 | dependencies = [ 476 | "libc", 477 | "log", 478 | "wasi", 479 | "windows-sys", 480 | ] 481 | 482 | [[package]] 483 | name = "num_cpus" 484 | version = "1.15.0" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" 487 | dependencies = [ 488 | "hermit-abi", 489 | "libc", 490 | ] 491 | 492 | [[package]] 493 | name = "once_cell" 494 | version = "1.17.0" 495 | source = "registry+https://github.com/rust-lang/crates.io-index" 496 | checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" 497 | 498 | [[package]] 499 | name = "openssl-probe" 500 | version = "0.1.5" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 503 | 504 | [[package]] 505 | name = "openssl-sys" 506 | version = "0.9.80" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" 509 | dependencies = [ 510 | "autocfg", 511 | "cc", 512 | "libc", 513 | "pkg-config", 514 | "vcpkg", 515 | ] 516 | 517 | [[package]] 518 | name = "parking" 519 | version = "2.0.0" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" 522 | 523 | [[package]] 524 | name = "percent-encoding" 525 | version = "2.2.0" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" 528 | 529 | [[package]] 530 | name = "pin-project" 531 | version = "1.0.12" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" 534 | dependencies = [ 535 | "pin-project-internal", 536 | ] 537 | 538 | [[package]] 539 | name = "pin-project-internal" 540 | version = "1.0.12" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" 543 | dependencies = [ 544 | "proc-macro2", 545 | "quote", 546 | "syn", 547 | ] 548 | 549 | [[package]] 550 | name = "pin-project-lite" 551 | version = "0.2.9" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 554 | 555 | [[package]] 556 | name = "pin-utils" 557 | version = "0.1.0" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 560 | 561 | [[package]] 562 | name = "pkg-config" 563 | version = "0.3.26" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" 566 | 567 | [[package]] 568 | name = "polling" 569 | version = "2.5.2" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "22122d5ec4f9fe1b3916419b76be1e80bcb93f618d071d2edf841b137b2a2bd6" 572 | dependencies = [ 573 | "autocfg", 574 | "cfg-if", 575 | "libc", 576 | "log", 577 | "wepoll-ffi", 578 | "windows-sys", 579 | ] 580 | 581 | [[package]] 582 | name = "pretend" 583 | version = "0.3.0" 584 | dependencies = [ 585 | "async-trait", 586 | "bytes", 587 | "encoding_rs", 588 | "http", 589 | "mime", 590 | "pretend-codegen", 591 | "serde", 592 | "serde_json", 593 | "serde_urlencoded", 594 | "thiserror", 595 | "url", 596 | ] 597 | 598 | [[package]] 599 | name = "pretend-codegen" 600 | version = "0.3.0" 601 | dependencies = [ 602 | "http", 603 | "lazy_static", 604 | "proc-macro2", 605 | "quote", 606 | "regex", 607 | "syn", 608 | ] 609 | 610 | [[package]] 611 | name = "pretend-isahc" 612 | version = "0.3.0" 613 | dependencies = [ 614 | "isahc", 615 | "pretend", 616 | ] 617 | 618 | [[package]] 619 | name = "pretend-reqwest" 620 | version = "0.3.0" 621 | dependencies = [ 622 | "pretend", 623 | "reqwest", 624 | ] 625 | 626 | [[package]] 627 | name = "pretend-test-default-feature-build" 628 | version = "0.0.0" 629 | dependencies = [ 630 | "anyhow", 631 | "pretend", 632 | "pretend-codegen", 633 | "pretend-isahc", 634 | "pretend-reqwest", 635 | "pretend-ureq", 636 | "thiserror", 637 | "tokio", 638 | "trybuild", 639 | "ureq", 640 | ] 641 | 642 | [[package]] 643 | name = "pretend-ureq" 644 | version = "0.3.0" 645 | dependencies = [ 646 | "pretend", 647 | "ureq", 648 | ] 649 | 650 | [[package]] 651 | name = "proc-macro2" 652 | version = "1.0.51" 653 | source = "registry+https://github.com/rust-lang/crates.io-index" 654 | checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" 655 | dependencies = [ 656 | "unicode-ident", 657 | ] 658 | 659 | [[package]] 660 | name = "quote" 661 | version = "1.0.23" 662 | source = "registry+https://github.com/rust-lang/crates.io-index" 663 | checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" 664 | dependencies = [ 665 | "proc-macro2", 666 | ] 667 | 668 | [[package]] 669 | name = "regex" 670 | version = "1.7.1" 671 | source = "registry+https://github.com/rust-lang/crates.io-index" 672 | checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" 673 | dependencies = [ 674 | "aho-corasick", 675 | "memchr", 676 | "regex-syntax", 677 | ] 678 | 679 | [[package]] 680 | name = "regex-syntax" 681 | version = "0.6.28" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" 684 | 685 | [[package]] 686 | name = "reqwest" 687 | version = "0.11.14" 688 | source = "registry+https://github.com/rust-lang/crates.io-index" 689 | checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9" 690 | dependencies = [ 691 | "base64 0.21.0", 692 | "bytes", 693 | "encoding_rs", 694 | "futures-core", 695 | "futures-util", 696 | "h2", 697 | "http", 698 | "http-body", 699 | "hyper", 700 | "ipnet", 701 | "js-sys", 702 | "log", 703 | "mime", 704 | "once_cell", 705 | "percent-encoding", 706 | "pin-project-lite", 707 | "serde", 708 | "serde_json", 709 | "serde_urlencoded", 710 | "tokio", 711 | "tower-service", 712 | "url", 713 | "wasm-bindgen", 714 | "wasm-bindgen-futures", 715 | "web-sys", 716 | "winreg", 717 | ] 718 | 719 | [[package]] 720 | name = "ryu" 721 | version = "1.0.12" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" 724 | 725 | [[package]] 726 | name = "schannel" 727 | version = "0.1.21" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" 730 | dependencies = [ 731 | "windows-sys", 732 | ] 733 | 734 | [[package]] 735 | name = "serde" 736 | version = "1.0.152" 737 | source = "registry+https://github.com/rust-lang/crates.io-index" 738 | checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" 739 | 740 | [[package]] 741 | name = "serde_derive" 742 | version = "1.0.152" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" 745 | dependencies = [ 746 | "proc-macro2", 747 | "quote", 748 | "syn", 749 | ] 750 | 751 | [[package]] 752 | name = "serde_json" 753 | version = "1.0.92" 754 | source = "registry+https://github.com/rust-lang/crates.io-index" 755 | checksum = "7434af0dc1cbd59268aa98b4c22c131c0584d2232f6fb166efb993e2832e896a" 756 | dependencies = [ 757 | "itoa", 758 | "ryu", 759 | "serde", 760 | ] 761 | 762 | [[package]] 763 | name = "serde_urlencoded" 764 | version = "0.7.1" 765 | source = "registry+https://github.com/rust-lang/crates.io-index" 766 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 767 | dependencies = [ 768 | "form_urlencoded", 769 | "itoa", 770 | "ryu", 771 | "serde", 772 | ] 773 | 774 | [[package]] 775 | name = "slab" 776 | version = "0.4.7" 777 | source = "registry+https://github.com/rust-lang/crates.io-index" 778 | checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" 779 | dependencies = [ 780 | "autocfg", 781 | ] 782 | 783 | [[package]] 784 | name = "sluice" 785 | version = "0.5.5" 786 | source = "registry+https://github.com/rust-lang/crates.io-index" 787 | checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" 788 | dependencies = [ 789 | "async-channel", 790 | "futures-core", 791 | "futures-io", 792 | ] 793 | 794 | [[package]] 795 | name = "socket2" 796 | version = "0.4.7" 797 | source = "registry+https://github.com/rust-lang/crates.io-index" 798 | checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" 799 | dependencies = [ 800 | "libc", 801 | "winapi", 802 | ] 803 | 804 | [[package]] 805 | name = "syn" 806 | version = "1.0.107" 807 | source = "registry+https://github.com/rust-lang/crates.io-index" 808 | checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" 809 | dependencies = [ 810 | "proc-macro2", 811 | "quote", 812 | "unicode-ident", 813 | ] 814 | 815 | [[package]] 816 | name = "termcolor" 817 | version = "1.2.0" 818 | source = "registry+https://github.com/rust-lang/crates.io-index" 819 | checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" 820 | dependencies = [ 821 | "winapi-util", 822 | ] 823 | 824 | [[package]] 825 | name = "thiserror" 826 | version = "1.0.38" 827 | source = "registry+https://github.com/rust-lang/crates.io-index" 828 | checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" 829 | dependencies = [ 830 | "thiserror-impl", 831 | ] 832 | 833 | [[package]] 834 | name = "thiserror-impl" 835 | version = "1.0.38" 836 | source = "registry+https://github.com/rust-lang/crates.io-index" 837 | checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" 838 | dependencies = [ 839 | "proc-macro2", 840 | "quote", 841 | "syn", 842 | ] 843 | 844 | [[package]] 845 | name = "tinyvec" 846 | version = "1.6.0" 847 | source = "registry+https://github.com/rust-lang/crates.io-index" 848 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 849 | dependencies = [ 850 | "tinyvec_macros", 851 | ] 852 | 853 | [[package]] 854 | name = "tinyvec_macros" 855 | version = "0.1.1" 856 | source = "registry+https://github.com/rust-lang/crates.io-index" 857 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 858 | 859 | [[package]] 860 | name = "tokio" 861 | version = "1.25.0" 862 | source = "registry+https://github.com/rust-lang/crates.io-index" 863 | checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" 864 | dependencies = [ 865 | "autocfg", 866 | "bytes", 867 | "libc", 868 | "memchr", 869 | "mio", 870 | "num_cpus", 871 | "pin-project-lite", 872 | "socket2", 873 | "tokio-macros", 874 | "windows-sys", 875 | ] 876 | 877 | [[package]] 878 | name = "tokio-macros" 879 | version = "1.8.2" 880 | source = "registry+https://github.com/rust-lang/crates.io-index" 881 | checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" 882 | dependencies = [ 883 | "proc-macro2", 884 | "quote", 885 | "syn", 886 | ] 887 | 888 | [[package]] 889 | name = "tokio-util" 890 | version = "0.7.4" 891 | source = "registry+https://github.com/rust-lang/crates.io-index" 892 | checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" 893 | dependencies = [ 894 | "bytes", 895 | "futures-core", 896 | "futures-sink", 897 | "pin-project-lite", 898 | "tokio", 899 | "tracing", 900 | ] 901 | 902 | [[package]] 903 | name = "tower-service" 904 | version = "0.3.2" 905 | source = "registry+https://github.com/rust-lang/crates.io-index" 906 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 907 | 908 | [[package]] 909 | name = "tracing" 910 | version = "0.1.37" 911 | source = "registry+https://github.com/rust-lang/crates.io-index" 912 | checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" 913 | dependencies = [ 914 | "cfg-if", 915 | "log", 916 | "pin-project-lite", 917 | "tracing-attributes", 918 | "tracing-core", 919 | ] 920 | 921 | [[package]] 922 | name = "tracing-attributes" 923 | version = "0.1.23" 924 | source = "registry+https://github.com/rust-lang/crates.io-index" 925 | checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" 926 | dependencies = [ 927 | "proc-macro2", 928 | "quote", 929 | "syn", 930 | ] 931 | 932 | [[package]] 933 | name = "tracing-core" 934 | version = "0.1.30" 935 | source = "registry+https://github.com/rust-lang/crates.io-index" 936 | checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" 937 | dependencies = [ 938 | "once_cell", 939 | ] 940 | 941 | [[package]] 942 | name = "tracing-futures" 943 | version = "0.2.5" 944 | source = "registry+https://github.com/rust-lang/crates.io-index" 945 | checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" 946 | dependencies = [ 947 | "pin-project", 948 | "tracing", 949 | ] 950 | 951 | [[package]] 952 | name = "try-lock" 953 | version = "0.2.4" 954 | source = "registry+https://github.com/rust-lang/crates.io-index" 955 | checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" 956 | 957 | [[package]] 958 | name = "trybuild" 959 | version = "1.0.77" 960 | source = "registry+https://github.com/rust-lang/crates.io-index" 961 | checksum = "a44da5a6f2164c8e14d3bbc0657d69c5966af9f5f6930d4f600b1f5c4a673413" 962 | dependencies = [ 963 | "basic-toml", 964 | "glob", 965 | "once_cell", 966 | "serde", 967 | "serde_derive", 968 | "serde_json", 969 | "termcolor", 970 | ] 971 | 972 | [[package]] 973 | name = "unicode-bidi" 974 | version = "0.3.10" 975 | source = "registry+https://github.com/rust-lang/crates.io-index" 976 | checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" 977 | 978 | [[package]] 979 | name = "unicode-ident" 980 | version = "1.0.6" 981 | source = "registry+https://github.com/rust-lang/crates.io-index" 982 | checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" 983 | 984 | [[package]] 985 | name = "unicode-normalization" 986 | version = "0.1.22" 987 | source = "registry+https://github.com/rust-lang/crates.io-index" 988 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 989 | dependencies = [ 990 | "tinyvec", 991 | ] 992 | 993 | [[package]] 994 | name = "ureq" 995 | version = "2.1.1" 996 | source = "registry+https://github.com/rust-lang/crates.io-index" 997 | checksum = "2475a6781e9bc546e7b64f4013d2f4032c8c6a40fcffd7c6f4ee734a890972ab" 998 | dependencies = [ 999 | "base64 0.13.1", 1000 | "chunked_transfer", 1001 | "log", 1002 | "once_cell", 1003 | "url", 1004 | ] 1005 | 1006 | [[package]] 1007 | name = "url" 1008 | version = "2.3.1" 1009 | source = "registry+https://github.com/rust-lang/crates.io-index" 1010 | checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" 1011 | dependencies = [ 1012 | "form_urlencoded", 1013 | "idna", 1014 | "percent-encoding", 1015 | ] 1016 | 1017 | [[package]] 1018 | name = "vcpkg" 1019 | version = "0.2.15" 1020 | source = "registry+https://github.com/rust-lang/crates.io-index" 1021 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1022 | 1023 | [[package]] 1024 | name = "waker-fn" 1025 | version = "1.1.0" 1026 | source = "registry+https://github.com/rust-lang/crates.io-index" 1027 | checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" 1028 | 1029 | [[package]] 1030 | name = "want" 1031 | version = "0.3.0" 1032 | source = "registry+https://github.com/rust-lang/crates.io-index" 1033 | checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" 1034 | dependencies = [ 1035 | "log", 1036 | "try-lock", 1037 | ] 1038 | 1039 | [[package]] 1040 | name = "wasi" 1041 | version = "0.11.0+wasi-snapshot-preview1" 1042 | source = "registry+https://github.com/rust-lang/crates.io-index" 1043 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1044 | 1045 | [[package]] 1046 | name = "wasm-bindgen" 1047 | version = "0.2.84" 1048 | source = "registry+https://github.com/rust-lang/crates.io-index" 1049 | checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" 1050 | dependencies = [ 1051 | "cfg-if", 1052 | "wasm-bindgen-macro", 1053 | ] 1054 | 1055 | [[package]] 1056 | name = "wasm-bindgen-backend" 1057 | version = "0.2.84" 1058 | source = "registry+https://github.com/rust-lang/crates.io-index" 1059 | checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" 1060 | dependencies = [ 1061 | "bumpalo", 1062 | "log", 1063 | "once_cell", 1064 | "proc-macro2", 1065 | "quote", 1066 | "syn", 1067 | "wasm-bindgen-shared", 1068 | ] 1069 | 1070 | [[package]] 1071 | name = "wasm-bindgen-futures" 1072 | version = "0.4.34" 1073 | source = "registry+https://github.com/rust-lang/crates.io-index" 1074 | checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" 1075 | dependencies = [ 1076 | "cfg-if", 1077 | "js-sys", 1078 | "wasm-bindgen", 1079 | "web-sys", 1080 | ] 1081 | 1082 | [[package]] 1083 | name = "wasm-bindgen-macro" 1084 | version = "0.2.84" 1085 | source = "registry+https://github.com/rust-lang/crates.io-index" 1086 | checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" 1087 | dependencies = [ 1088 | "quote", 1089 | "wasm-bindgen-macro-support", 1090 | ] 1091 | 1092 | [[package]] 1093 | name = "wasm-bindgen-macro-support" 1094 | version = "0.2.84" 1095 | source = "registry+https://github.com/rust-lang/crates.io-index" 1096 | checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" 1097 | dependencies = [ 1098 | "proc-macro2", 1099 | "quote", 1100 | "syn", 1101 | "wasm-bindgen-backend", 1102 | "wasm-bindgen-shared", 1103 | ] 1104 | 1105 | [[package]] 1106 | name = "wasm-bindgen-shared" 1107 | version = "0.2.84" 1108 | source = "registry+https://github.com/rust-lang/crates.io-index" 1109 | checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" 1110 | 1111 | [[package]] 1112 | name = "web-sys" 1113 | version = "0.3.61" 1114 | source = "registry+https://github.com/rust-lang/crates.io-index" 1115 | checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" 1116 | dependencies = [ 1117 | "js-sys", 1118 | "wasm-bindgen", 1119 | ] 1120 | 1121 | [[package]] 1122 | name = "wepoll-ffi" 1123 | version = "0.1.2" 1124 | source = "registry+https://github.com/rust-lang/crates.io-index" 1125 | checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" 1126 | dependencies = [ 1127 | "cc", 1128 | ] 1129 | 1130 | [[package]] 1131 | name = "winapi" 1132 | version = "0.3.9" 1133 | source = "registry+https://github.com/rust-lang/crates.io-index" 1134 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1135 | dependencies = [ 1136 | "winapi-i686-pc-windows-gnu", 1137 | "winapi-x86_64-pc-windows-gnu", 1138 | ] 1139 | 1140 | [[package]] 1141 | name = "winapi-i686-pc-windows-gnu" 1142 | version = "0.4.0" 1143 | source = "registry+https://github.com/rust-lang/crates.io-index" 1144 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1145 | 1146 | [[package]] 1147 | name = "winapi-util" 1148 | version = "0.1.5" 1149 | source = "registry+https://github.com/rust-lang/crates.io-index" 1150 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1151 | dependencies = [ 1152 | "winapi", 1153 | ] 1154 | 1155 | [[package]] 1156 | name = "winapi-x86_64-pc-windows-gnu" 1157 | version = "0.4.0" 1158 | source = "registry+https://github.com/rust-lang/crates.io-index" 1159 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1160 | 1161 | [[package]] 1162 | name = "windows-sys" 1163 | version = "0.42.0" 1164 | source = "registry+https://github.com/rust-lang/crates.io-index" 1165 | checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" 1166 | dependencies = [ 1167 | "windows_aarch64_gnullvm", 1168 | "windows_aarch64_msvc", 1169 | "windows_i686_gnu", 1170 | "windows_i686_msvc", 1171 | "windows_x86_64_gnu", 1172 | "windows_x86_64_gnullvm", 1173 | "windows_x86_64_msvc", 1174 | ] 1175 | 1176 | [[package]] 1177 | name = "windows_aarch64_gnullvm" 1178 | version = "0.42.1" 1179 | source = "registry+https://github.com/rust-lang/crates.io-index" 1180 | checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" 1181 | 1182 | [[package]] 1183 | name = "windows_aarch64_msvc" 1184 | version = "0.42.1" 1185 | source = "registry+https://github.com/rust-lang/crates.io-index" 1186 | checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" 1187 | 1188 | [[package]] 1189 | name = "windows_i686_gnu" 1190 | version = "0.42.1" 1191 | source = "registry+https://github.com/rust-lang/crates.io-index" 1192 | checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" 1193 | 1194 | [[package]] 1195 | name = "windows_i686_msvc" 1196 | version = "0.42.1" 1197 | source = "registry+https://github.com/rust-lang/crates.io-index" 1198 | checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" 1199 | 1200 | [[package]] 1201 | name = "windows_x86_64_gnu" 1202 | version = "0.42.1" 1203 | source = "registry+https://github.com/rust-lang/crates.io-index" 1204 | checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" 1205 | 1206 | [[package]] 1207 | name = "windows_x86_64_gnullvm" 1208 | version = "0.42.1" 1209 | source = "registry+https://github.com/rust-lang/crates.io-index" 1210 | checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" 1211 | 1212 | [[package]] 1213 | name = "windows_x86_64_msvc" 1214 | version = "0.42.1" 1215 | source = "registry+https://github.com/rust-lang/crates.io-index" 1216 | checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" 1217 | 1218 | [[package]] 1219 | name = "winreg" 1220 | version = "0.10.1" 1221 | source = "registry+https://github.com/rust-lang/crates.io-index" 1222 | checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" 1223 | dependencies = [ 1224 | "winapi", 1225 | ] 1226 | --------------------------------------------------------------------------------