From 200366382683037e1cc1a06fba5bf150f1204ca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20Juhos?= Date: Sun, 31 Jan 2021 22:12:17 +0100 Subject: [PATCH] The Big Public API Documentation Update, volume 3. (#196) * Rework and update getting-started.md * Reword parts of the API docs in annotations.dart * Add description of path resolution behavior in requests.md This commit addresses issue #195 * Reword parts of the documentations in requests.md * Reword the error handling description in getting-started.md * Reword and update most of the documentation in requests.md * Add missing @s to annotation mentions in getting-started.md * Remove an unnecessary new line from getting-started.md * Add further explanation to an example service's create method in getting-started.md * Update the README of chopper_built_value * Update the README of chopper_generator * Update and reword top level README.md and chopper/README.md * Try to fix text formatting in requests.md * Fix wording requests.md's form URL encoded section * Remove unsafe hint on build_runner from getting-started.md --- README.md | 25 ++---- chopper/README.md | 8 +- chopper/lib/src/annotations.dart | 4 +- chopper_built_value/README.md | 2 +- chopper_generator/README.md | 2 +- getting-started.md | 86 +++++++++++--------- requests.md | 130 +++++++++++++++++++++++-------- 7 files changed, 164 insertions(+), 93 deletions(-) diff --git a/README.md b/README.md index 0a18133e..2d6f5747 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,23 @@ ---- -description: Chopper is an http client generator using source_gen and inspired by Retrofit. ---- - # Chopper [![pub package](https://img.shields.io/pub/v/chopper.svg)](https://pub.dartlang.org/packages/chopper) [![Build Status](https://travis-ci.com/lejard-h/chopper.svg?branch=master)](https://travis-ci.com/lejard-h/chopper.svg?branch=master) [![codecov](https://codecov.io/gh/lejard-h/chopper/branch/master/graph/badge.svg)](https://codecov.io/gh/lejard-h/chopper) -[Documentations](https://hadrien-lejard.gitbook.io/chopper) - -## Installation +Chopper is an http client generator for Dart and Flutter using source_gen and inspired by Retrofit. -```yaml -# pubspec.yaml +[Documentation](https://hadrien-lejard.gitbook.io/chopper) -dependencies: - chopper: ^3.0.2 +## Installation -dev_dependencies: - build_runner: ^1.0.0 - chopper_generator: ^3.0.4 -``` +Please refer to the installation guide at [pub.dev](https://pub.dev/packages/chopper/install) or in the [Getting started](getting-started.md) document. -* [Getting started](getting-started.md) +* [Requests](requests.md) * [Converters](converters/converters.md) * [Interceptors](interceptors.md) ## Examples -* [Json serializable](https://github.com/lejard-h/chopper/blob/master/example/bin/main_json_serializable.dart) -* [Built Value](https://github.com/lejard-h/chopper/blob/master/example/bin/main_built_value.dart) +* [json serializable Converter](https://github.com/lejard-h/chopper/blob/master/example/bin/main_json_serializable.dart) +* [built value Converter](https://github.com/lejard-h/chopper/blob/master/example/bin/main_built_value.dart) * [Angular](https://github.com/lejard-h/chopper/blob/master/example/web/main.dart) ## [Issue Tracker](https://github.com/lejard-h/chopper/issues) diff --git a/chopper/README.md b/chopper/README.md index ec24a6bb..e728fce2 100644 --- a/chopper/README.md +++ b/chopper/README.md @@ -2,6 +2,8 @@ [![pub package](https://img.shields.io/pub/v/chopper.svg)](https://pub.dartlang.org/packages/chopper) [![Build Status](https://travis-ci.com/lejard-h/chopper.svg?branch=master)](https://travis-ci.org/lejard-h/chopper) [![codecov](https://codecov.io/gh/lejard-h/chopper/branch/master/graph/badge.svg)](https://codecov.io/gh/lejard-h/chopper) +Chopper is an http client generator for Dart and Flutter using source_gen and inspired by Retrofit. + [**Documentation**](https://hadrien-lejard.gitbook.io/chopper) ## Adding Chopper to your project @@ -37,9 +39,9 @@ Latest versions: ## Examples -* [Json serializable](https://github.com/lejard-h/chopper/blob/master/example/bin/main_json_serializable.dart) -* [Built Value](https://github.com/lejard-h/chopper/blob/master/example/bin/main_built_value.dart) +* [json_serializable Converter](https://github.com/lejard-h/chopper/blob/master/example/bin/main_json_serializable.dart) +* [built_value Converter](https://github.com/lejard-h/chopper/blob/master/example/bin/main_built_value.dart) * [Angular](https://github.com/lejard-h/chopper/blob/master/example/web/main.dart) -## If you encounter any issues, or need a feature implemented, please visit [Chopper's Issue Tracker](https://github.com/lejard-h/chopper/issues). +## If you encounter any issues, or need a feature implemented, please visit [Chopper's Issue Tracker on GitHub](https://github.com/lejard-h/chopper/issues). diff --git a/chopper/lib/src/annotations.dart b/chopper/lib/src/annotations.dart index b556e4ec..1c628637 100644 --- a/chopper/lib/src/annotations.dart +++ b/chopper/lib/src/annotations.dart @@ -20,7 +20,7 @@ import 'constants.dart'; /// See [Method] to define an HTTP request @immutable class ChopperApi { - /// Url that will prefix every request define inside that API. + /// A part of a URL that every request defined inside a class annotated with [ChopperApi] will be prefixed with. final String baseUrl; const ChopperApi({ @@ -43,7 +43,7 @@ class ChopperApi { @immutable class Path { /// Name is used to bind a method parameter to - /// the url parameter. + /// a URL path parameter. /// ```dart /// @Get(path: '/{param}') /// Future fetch(@Path('param') String hello); diff --git a/chopper_built_value/README.md b/chopper_built_value/README.md index 455791c5..f12bc0c2 100644 --- a/chopper_built_value/README.md +++ b/chopper_built_value/README.md @@ -1 +1 @@ -[Chopper](https://github.com/lejard-h/chopper) is an http client generator using source_gen, inspired by Retrofit. \ No newline at end of file +This package provides a Converter based on built_value that can be used with [Chopper](https://github.com/lejard-h/chopper) to convert objects to JSON and vice versa. diff --git a/chopper_generator/README.md b/chopper_generator/README.md index 455791c5..63f60659 100644 --- a/chopper_generator/README.md +++ b/chopper_generator/README.md @@ -1 +1 @@ -[Chopper](https://github.com/lejard-h/chopper) is an http client generator using source_gen, inspired by Retrofit. \ No newline at end of file +This package provides the code generator for the [Chopper](https://github.com/lejard-h/chopper) package. diff --git a/getting-started.md b/getting-started.md index 2200a952..19ed17ac 100644 --- a/getting-started.md +++ b/getting-started.md @@ -1,31 +1,32 @@ # Getting started -## How it works ? +## How does Chopper work? -Due to Dart limitation on Flutter and Web browser, Chopper does not use reflection but code generation with the help of the [build](https://pub.dev/packages/build) and [source\_gen](https://pub.dev/packages/source_gen) packages from the Dart Team. +Due to limitations to Dart on Flutter and the Web browser, Chopper doesn't use reflection but code generation with the help of the [build](https://pub.dev/packages/build) and [source\_gen](https://pub.dev/packages/source_gen) packages from the Dart Team. ## Installation -First you need to add `chopper` and `chopper_generator` to your project dependencies. +Add the `chopper` and the `chopper_generator` packages to your project dependencies. ```yaml # pubspec.yaml dependencies: - chopper: ^2.0.0 + chopper: ^3.0.7 dev_dependencies: build_runner: ^1.0.0 - chopper_generator: ^2.0.0 + chopper_generator: ^3.0.7 ``` -And run `pub get` +Run `pub get` to start using Chopper in your project. ## Define your API ### ChopperApi -To define a client, use the `ChopperApi` annotation on a class and extends the `ChopperService` client. +To define a client, use the `@ +ChopperApi` annotation on an abstract class that extends the `ChopperService` class. ```dart // YOUR_FILE.dart @@ -33,36 +34,59 @@ To define a client, use the `ChopperApi` annotation on a class and extends the ` import "dart:async"; import 'package:chopper/chopper.dart'; -// this is necessary for the generated code to find your class +// This is necessary for the generator to work. part "YOUR_FILE.chopper.dart"; @ChopperApi(baseUrl: "/todos") abstract class TodosListService extends ChopperService { - // helper methods that help you instantiate your service + // A helper method that helps instantiating the service. You can omit this method and use the generated class directly instead. static TodosListService create([ChopperClient client]) => _$TodosListService(client); } ``` -`ChopperApi` annotation takes one optional parameter, the `baseUrl` that will prefix all the request define in the class. +The `@ChopperApi` annotation takes one optional parameter - the `baseUrl` - that will prefix all the request's URLs defined in the class. -### Define a request +> There's an exception from this behavior described in the [Requests](requests.md) section of the documentation. -To define a request, use one of the following annotations `Get`, `Post`, `Put`, `Patch`, `Delete` and must return a `Future` +### Defining a request -Let's say you want to do a `GET` request on the following endpoint `/todos/TODO_ID`, add the following method declaration to your class. +Use one of the following annotations on abstract methods of a service class to define requests: + +* `@Get` + +* `@Post` + +* `@Put` + +* `@Patch` + +* `@Delete` + +* `@Head` + +Request methods must return with values of the type `Future` or `Future>`. + +To define a `GET` request to the endpoint `/todos` in the service class above, add one of the following method declarations to the class: + +```dart +@Get() +Future getTodos(); +``` + +or ```dart -@Get(path: '/{id}') -Future getTodo(@Path() String id); +@Get() +Future>> getTodos(); ``` -Using `{id}` and the `Path` annotation your are telling chopper to replace `{id}` in the url by the value of the `id` parameter. +URL manipulation with dynamic path, and query parameters is also supported. To learn more about URL manipulation with Chopper, have a look at the [Requests](requests.md) section of the documentation. -## ChopperClient +## Defining a ChopperClient -After defining your `ChopperService` you need to attribute a `ChopperClient` to it. The `ChopperClient` will manage the server hostname to call and can handle multiple `ChopperService`. It is also responsible of applying [interceptors](interceptors.md) and [converter]() to your requests. +After defining one or more `ChopperService`s, you need to bind instances of them to a `ChopperClient`. The `ChopperClient` provides the base URL for every service and it is also responsible for applying [interceptors](interceptors.md) and [converters](converters/converters.md) on the requests it handles. ```dart import "dart:async"; @@ -74,36 +98,28 @@ void main() async { final chopper = ChopperClient( baseUrl: "http://my-server:8000", services: [ - // inject the generated service + // Create and pass an instance of the generated service to the client TodosListService.create() ], ); - /// retrieve your service + /// Get a reference to the client-bound service instance... final todosService = chopper.getService(); - /// or create a new one - final todosService = TodosListService.create(chopper); + /// ... or create a new instance by explicitly binding it to a client. + final anotherTodosService = TodosListService.create(chopper); - /// then call your function - final response = await todosService.getTodosList(); + /// Making a request is as easy as calling a function of the service. + final response = await todosService.getTodos(); if (response.isSuccessful) { - // successful request + // Successful request final body = response.body; } else { - // error from server + // Error code received from server final code = response.statusCode; final error = response.error; } } - ``` - - -### - - - - - +Handling I/O and other exceptions should be done by surrounding requests with `try-catch` blocks. diff --git a/requests.md b/requests.md index a5547764..cee349a1 100644 --- a/requests.md +++ b/requests.md @@ -1,40 +1,85 @@ # Requests +## Path resolution + +Chopper handles paths passed to HTTP verb annotations' `path` parameter based on the path's content. + +If the `path` value is a relative path, it will be concatenated to the URL composed of the `baseUrl` of the `ChopperClient` and the `baseUrl` of the enclosing service class (provided as a parameter of the `@ChopperApi` annotation). + +Here are a few examples of the described behavior: + +* `ChopperClient` base URL: https://example.com/ + Path: profile + Result: https://example.com/profile + +* `ChopperClient` base URL: https://example.com/ + Service base URL: profile + Path: /image + Result: https://example.com/profile/image + +* `ChopperClient` base URL: https://example.com/ + Service base URL: profile + Path: image + Result: https://example.com/profile/image + +> Chopper detects and handles missing slash (`/`) characters on URL segment borders, but *does not* handle duplicate slashes. + +If the service's `baseUrl` concatenated with the request's `path` results in a full URL, the `ChopperClient`'s `baseUrl` is ignored. + +* `ChopperClient` base URL: https://example.com/ +Service base URL: https://api.github.com/ +Path: user +Result: https://api.github.com/user + +A `path` containing a full URL replaces the base URLs of both the `ChopperClient` and the service class entirely for a request. + +* `ChopperClient` base URL: https://example.com/ + Path: https://api.github.com/user + Result: https://api.github.com/user + +* `ChopperClient` base URL: https://example.com/ + Service base URL: profile + Path: https://api.github.com/user + Result: https://api.github.com/user + ## Path parameters -Use `{}`to specify the name and the position of your parameter directly in the url +Dynamic path parameters can be defined in the URL with replacement blocks. A replacement block is an alphanumeric substring of the path surrounded by `{` and `}`. In the following example `{id}` is a replacement block. ```dart -@Get(path: '/{id}') +@Get(path: "/{id}") ``` -Then bind it to your method using the `Path` annotation. +Use the `@Path()` annotation to bind a parameter to a replacement block. This way the parameter's name must match a replacement block's string. ```dart -Future getById(@Path() String id); +@Get(path: "/{id}") +Future getItemById(@Path() String id); ``` -or +As an alternative, you can set the `@Path` annotation's `name` parameter to match a replacement block's string while using a different parameter name, like in the following example: ```dart -Future getById(@Path('id') int ref); +@Get(path: "/{id}") +Future getItemById(@Path("id") int itemId); ``` -Chopper will use the `toString` method to concat the url with the parameter. +> Chopper uses String interpolation to replace replacement blocks with the provided values in the request URLs. ## Query parameters -Use the `Query` annotation to add query parameters to the url +Dynamic query parameters can be added to the URL by adding parameters to a request method annotated with the `@Query` annotation. Default values are supported. ```dart -Future search({ - @Query() String name, - @Query('int') int number, - @Query('default_value') int def = 42, +Future search( + @Query() String name, { + @Query("count") int numberOfResults = 42, }); ``` -If you prefer to pass to pass a full `Map` you can use the `QueryMap` annotation +If the parameter of the `@Query` annotation is not set, Chopper will use the actual name of the annotated parameter as the key for the query parameter in the URL. + +If you prefer to pass a `Map` of query parameters, you can do so with the `@QueryMap` annotation. ```dart Future search(@QueryMap() Map query); @@ -42,37 +87,46 @@ Future search(@QueryMap() Map query); ## Request body -Use `Body` annotation to specify data to send. +Use the `@Body` annotation on a request method parameter to specify data that will be sent as the request's body. ```dart -@Post(path: "post") +@Post(path: "todo/create") Future postData(@Body() String data); ``` {% hint style="warning" %} -Chopper does not automatically convert `Object` to `Map`then `JSON` +Chopper does not automatically convert `Object`s to `Map`then `JSON`. -A [Converter](converters/converters.md) is necessary to do that, see [built\_value\_converter](converters/built-value-converter.md#built-value) for more infos. +You have to pass a [Converter](converters/converters.md) instance to a `ChopperClient` for JSON conversion to happen. See [built\_value\_converter](converters/built-value-converter.md#built-value) for an example Converter implementation. {% endhint %} ## Headers -Request headers can be set using [Interceptor](interceptors.md) or [Converter](converters/converters.md), but also on the Method definition. +Request headers can be set by providing a `Map` object to the `headers` parameter each of the HTTP verb annotations have. ```dart -@Get(path: '/', headers: {'foo': 'bar'}) +@Get(path: "/", headers: {"foo": "bar"}) Future fetch(); +``` + +The `@Header` annotation can be used on method parameters to set headers dynamically for each request call. -/// dynamic -@Get(path: '/') -Future fetch(@Header('foo') String bar); +```dart +@Get(path: "/") +Future fetch(@Header("foo") String bar); ``` -## Send application/x-www-form-urlencoded +> Setting request headers dynamically is also supported by [Interceptors](interceptors.md) and [Converters](converters/converters.md). +> +> As Chopper invokes Interceptors and Converter(s) *after* creating a Request, Interceptors and Converters *can* override headers set with the `headers` parameter or `@Header` annotations. + +## Sending `application/x-www-form-urlencoded` data -If no converter specified and if you are just using `Map` as body. This is the default behavior of the http package. +If no Converter is specified for a request (neither on a `ChopperClient` nor with the `@FactoryConverter` annotation) and the request body is of type `Map`, the body will be sent as form URL encoded data. -You can also use `FormUrlEncodedConverter` that will add the correct `content-type` and convert simple `Map` into `Map` to all request. +> This is the default behavior of the http package. + +You can also use `FormUrlEncodedConverter` that will add the correct `content-type` and convert a `Map` into `Map` for requests. ```dart final chopper = ChopperClient( @@ -80,22 +134,32 @@ final chopper = ChopperClient( ); ``` -#### On single method +### On a single method -If you wish to do a form urlencoded request on a single request, you can use a factory converter. +To do only a single type of request with form encoding in a service, use the provided `FormUrlEncodedConverter`'s `requestFactory` method with the `@FactoryConverter` annotation. ```dart -@Post(path: 'form', headers: {contentTypeKey: formEncodedHeaders}) +@Post( + path: "form", + headers: {contentTypeKey: formEncodedHeaders}, +) +@FactoryConverter( + request: FormUrlEncodedConverter.requestFactory, +) Future postForm(@Body() Map fields); ``` -#### Use Field annotation +### Defining fields individually -To specify each fields manually, use the `Field` annotation. +To specify fields individually, use the `@Field` annotation on method parameters. If the field's name is not provided, the parameter's name is used as the field's name. ```dart - @FactoryConverter(request: FormUrlEncodedConverter.requestFactory) - @Post(path: 'form') - Future post(@Field() String foo, @Field('b') int bar); +@Post(path: "form") +@FactoryConverter( + request: FormUrlEncodedConverter.requestFactory, +) +Future post(@Field() String foo, @Field("b") int bar); ``` +## Sending files +