lejard-h / chopper Goto Github PK
View Code? Open in Web Editor NEWChopper is an http client generator using source_gen and inspired from Retrofit.
Home Page: https://hadrien-lejard.gitbook.io/chopper
License: Other
Chopper is an http client generator using source_gen and inspired from Retrofit.
Home Page: https://hadrien-lejard.gitbook.io/chopper
License: Other
Hi, I'd like to make a suggestion!
In my opinion, It would be awesome if generator get default values for annotations parameters (Query, Path)
To explain beter, I'd like to provide an example
@Get(url: "users")
Future<Response> fetchUsers({ @Query() int limit=300 });
Generates:
Future<Response> fetchUsers({int limit=300}) {}
Or Maybe if annotations got new property, and you can send to generator
class Query {
final String name;
final dynamic defaultValue;
...
}
And use:
@Get(url: "users")
Future<Response> fetchUsers({ @Query(defaultValue: 300) int limit });
I want upload a image to avatar of user:
`List bytes = image?.readAsBytesSync();
@post(path: "/user/patient/uploadAvatar")
@multipart
Future updateAvatar(@FileField('avatar') List bytes);`
I didn't find any example and the test case was not very helpful.
is there @FormUrlEncoded
or any other way that I missed?
Very weird issue:
try {
_chopper = ChopperClient(
baseUrl: SERVER_ADDRESS,
client: DigestAuthClient(username, password),
services: [
_service = ApiService.create(_chopper)
],
converter: JsonConverter(),
errorConverter: JsonConverter(),
);
final response = await _service.login();
print('after login');
final int authErrCode = response.statusCode;
print(authErrCode);
...
If i input proper credentials server responds with 200 and the code past final response = await _service.login();
executes.
However, if I input incorrect credentials no code past final response = await _service.login();
executes(even tho I do get a 401 response in postman), and I catch an error:
2019-04-29 14:28:59.624475: error in login(): Instance of 'Response<dynamic>':
I/flutter ( 5328): #0 ChopperClient.send (package:chopper/src/base.dart:182:7)
I/flutter ( 5328): <asynchronous suspension>
I/flutter ( 5328): #1 _$ApiService.login (file:///C:///hellogithub/lib/api/api_service.chopper.dart:56:19)
I/flutter ( 5328): #2 BlueApi.login (//api/blue_api.dart:37:39)
Why would I be catching such an error only if server responds with something but 200?
This has always been around, but has become a bit more prominent due to the changes introduced in response to #10 which introduced a change that means Request
now has two parameters (among others) called baseUrl
and url
.
The meaning of baseUrl
is pretty clear (to me, anyway), however, it is I feel that it is unclear what url
means without digging into the source code. On the surface, there's no hint as to how it relates to the baseURL
parameter? Does it replace it, do they work together, etc?
As it turns out, this parameter (also exposed via the url
annotation) is not really a "url"... it is actually a "path" that gets appended to baseUrl
. In fact, if you pass a fully qualified URL (eg. 'https://my.api.com/api/v1/users'
) then it still gets blindly concatenated with baseUrl
and you end up with a malformed url (I'm just about to submit a pull request that addresses this).
Anyway, I'm not meaning to be difficult, but I think that naming of things is important. Ideally, I'd love to see this property renamed to something like path
(or perhaps urlSuffix
, because baseURL
may already actually contain a partial path).
I understand this is a breaking change, but just wanted to start a discussion to see what your thoughts are.
@JsonSerializable()
class Resource {
final String type;
final String id;
final String name;
Resource(this.type, this.id, this.name);
static const fromJsonFactory = _$ResourceFromJson;
Map<String, dynamic> toJson() => _$ResourceToJson(this);
}
@JsonSerializable()
class ResourceA extends Resource {
final String another;
ResourceA(String id, String name, this.another):super('A', id, name);
static const fromJsonFactory = _$ResourceAFromJson;
Map<String, dynamic> toJson() => _$ResourceAToJson(this);
}
The generated Map<String, dynamic> _$ResourceAToJson(ResourceA instance) does not include writeNotNull('type', instance.type) because it is not included in the constructor's parameters.
@lejard-h, Even though the latest version passes in the Body and Item types into _decodeResponse
, the same can't be said when the response isn't successful. This is causing it to fail when trying to decode to the ResultType in the Converter due to mismatched types after deserialization.
type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'MyBuiltValueResponse'
if (res.isSuccessful) {
if (responseConverter != null) {
res = await responseConverter(res);
} else {
res = await _decodeResponse<Body, Item>(res, converter);
}
} else {
res = await _decodeResponse(res, errorConverter);
}
As I see from the base.dart file, the order will be:
[ENCODE -> REQUEST -> DECODE INTERCEPT -> RESPONSE INTERCEPT]
But from my point of view, it should be
[ENCODE -> REQUEST INTERCEPT -> RESPONSE INTERCEPT -> DECODE ]
Not sure if I am right, but I can give an example. For eg, my response should be encrypted from the request and decrypted from the response. Then the flow will be:
[ENCODE(JSON MAPPING) -> REQUEST INTERCEPT(ENCRYPTED) -> RESPONSE INTERCEPT (DECRYPTED) -> DECODE(JSON MAPPING) ]
As the current flow, the decode is being called before the response intercept. Then I need to put too much logic on the Decode converter to handle the decryption, validate if the response is correct or not before decryption, etc... but It should only handle the JSON converter.
How do u think about it?
The current method of sending a file or image is using List<int> bytes
but this removes important info like fileName and contentType
is it possible to use MultipartFile
like this:
https://stackoverflow.com/a/49645074/3998402
Since build_runner >=1.2.0, it requires build_daemon 0.2.0 at least.
packages get
fails since chopper_generator is incompatible with build_daemon (tested in version 2.3.0)
Therefore, it is impossible to use chopper_generator with newer versions of build_runner.
I quote terminal output:
Because no versions of chopper_generator match >2.3.0 <3.0.0 and chopper_generator 2.3.0 depends on built_collection ^3.1.3, chopper_generator ^2.3.0 requires built_collection ^3.1.3.
So, because every version of build_daemon depends on built_collection ^4.1.0, chopper_generator ^2.3.0 is incompatible with build_daemon.
Because build_runner >=1.2.6 <1.3.0 depends on build_daemon ^0.4.0 and build_runner >=1.2.0 <1.2.6 depends on build_daemon ^0.2.0, build_runner >=1.2.0 <1.3.0 requires build_daemon ^0.2.0 or ^0.4.0.
And because build_runner >=1.3.0 depends on build_daemon ^0.5.0, build_runner >=1.2.0 requires build_daemon ^0.2.0 or ^0.4.0 or ^0.5.0.
And because chopper_generator ^2.3.0 is incompatible with build_daemon (1), chopper_generator ^2.3.0 is incompatible with build_runner >=1.2.0.
So, because nemobile_real_estate depends on both build_runner ^1.2.0 and chopper_generator ^2.3.0, version solving failed.
Hi lejard-h,
When ChopperClient#dispose called, it means all resources and references will be released.
I think it will be better if the references of service instances and httpClient can be released on dispose.
Or the client instance and service instances will be pairs of circular reference, and will never be released from memory.
So the solution may make some parts of the member field mutable, and set null on dispose.
Hi lejard-h,
For base.dart and request.dart, I think it may better that move the code bellow into Converter implementations.
/// in base.dart
final bool jsonApi;
final bool formUrlEncodedApi;
/// in request.dart
final bool multipart;
final bool json;
By doing those, it'll be easier to implement the multiple converter within a single client instance.
If an API call is unsuccessful (not in the 200-300 response code range), an exception is thrown. Upon further inspection, I see it's not actually an exception. The send
method from the base ChopperClient throws the Response instead of returning it. I think it was meant to be returned rather than thrown. Here is the line I am talking about:
chopper/chopper/lib/src/base.dart
Line 193 in dab25de
If this was intentional, I don't think it's a good choice. Exceptions should be thrown in exceptional cases. Non-200 responses are expected results in most APIs and should be handled with business logic by users of this library, rather than catching exceptions.
I Try to mock service using mockinto but It's hard to test the Response
, how i mock it?. This is example of use case:
final todo =BuiltList(List.generate(10, (index){
return Todo((b) =>
b.id = index,
b.text = 'todo $index'
);
}));
// the service
class TodoApiMock extends Mock implements TodoApi {}
final api = TodoApiMock();
// This is where the problem
when(api.fetchTodos(type(any as ArgMatcher)))
.then((_) async => Response(http.Response('', 200), todo));
It cause the reference error when using the @Path
decorator below.
@Get(url: "example/{val1}t")
Future<Response> getFunc({
@required @Path("val1") String val1,
});
The generated code will be:
...
final url = 'example/$val1t';
...
And there is no variable named "val1t".
Since new build version is out with breaking changes, chopper should also use it.
in this example
@Post(path: "store/cities/search")
Future<Response<CitiesResponse>> getCities(
@Field("key0") int key0,
@Field("key1") int key1,
);
if I call the method with null value the value should be ignored this is the default behavior of Retrofit
I suggest the generated code should be:
final $body = {
'key0': key0,
'key1': key1,
}..removeWhere((key, value) => value == null);
if you are interested I can start working on it.
I think it is time to cover this package with tests. At the moment chopper is already on 2.2.0 version and it is time to pay attention on testing
Hello. Is digest authentication supported? In that case, could you provide a usage example? Thank you.
I have this endpoint.
@Post(path: "details")
@multipart
Future<Response<User>> editProfile({
@Field("carcolor") String carColor,
@FileField() MultipartFile insurancepic,
});
when I leave it as is only FileField
is sent
but if I remove FileField
@Field("carcolor")
is sent with no problem.
@Post(path: "details")
@multipart
Future<Response<User>> editProfile({
@Field("carcolor") String carColor,
});
when I log the request using an interceptor request.body
is shows carcolor
which mean the problem probably happens after the interceptors are invoked.
In function body we can use type parameter T instead of argument type to get corresponding service:
T service<T extends ChopperService>() {
final s = _services[T];
if (s == null) {
throw Exception("Service of type '$type' not found.");
}
return s;
}
It leads us to more concise code:
final api = chopperClient.service<UserApi>();
or
UserApi api = chopperClient.service();
Compare with old version:
final api = chopperClient.service<UserApi>(UserApi);
Is it possible to add query parameters to all requests like "?key=123"?
I have @Field("name\$re") String name,
in my request but in the generated code the escape is ignored
the generated code:
'name$re': name,
this is a huge problem for me as I can't use the library at all.
Hi, would it be possible to change api slightly?
Currently if we write definition interface
@ChopperApi("MyService", baseUrl: "/resources")
abstract class MyServiceDefinition {
//code here
}
MyService
class is generated and developers import this in their code. Problem with this approach is that if we want to refactor class name, then we need to change import in all places we use that class.
I think chopper could use similar approach to built_value
- generate class, based on currents name but add _$
prefix. Then we could specify necessary method to create MyServiceDefinition
class.
example:
@ChopperApi(baseUrl: "/resources")
abstract class SampleService {
/**
* This should be added by developers by our specified convention
*/
static SampleService create(ChopperConfig config) {
return _$SampleService(config);
}
@Get("/todos/1")
Future<TodoDto> getTodo();
}
By enforcing static method current problem would be resolved.
I created something like this in my custom example here:
https://github.com/charafau/retrofit.dart
What do you think about this?
@Post(url: "api")
@FormUrlEncoded()
Future<Response<SomeResponse> postRequest({@field("param1") int param1});
When call the method with a null value parameter "param1", it will be sent as an empty String.
I tried to use the chopper, unfortunately, got an error related to the analyzer
Because chopper_generator >=2.3.3 depends on analyzer ^0.35.0 and mobx_codegen >=0.1.3 depends on analyzer ^0.36.3, chopper_generator >=2.3.3 is incompatible with mobx_codegen >=0.1.3.
Something like HttpLoggingInterceptor in retrofit. Can you show to me example code about that?
@Post(path:url)
@multipart
Future<Response> postFileAndData(
@Query("type") int type,
@Query("title")
String title,
@Query("email") String email ,
@Query("owner") String owner,
@Query("isFileMandatory") bool isFileMandatory,
@Part("formdata") var jsondata,
@PartFile("file") List<PartFile> file);
List<http.MultipartFile> list = new List();
if (_paths != null) {
var multipat = await http.MultipartFile.fromPath(
"file", _paths.values.toList()[0].toString());
list.add(multipat);
}
var apiService = PostApiService.create();
final response = await apiService.postFileAndData(
2,
title,
"[email protected]",
"[email protected]",
false,
dataPostJson,
null);
// i want to pass the list in the place of null
Since v2.x ChopperClient
now has a couple of new function signatures:
Future<Response<Body>> send<Body, Item>()
Future<Response<ResultType>> post<ResultType, ItemType>()
...
I must admit I have no idea what the differences are between Body
, Item
, ResultType
, and ItemType
. Are the Body
/Item
combo essentially representing the same thing as ResultType
/ItemType
, or are they totally different? Most (all?) of the examples have Body
and Item
being set to the same type... when would they be different?
The notion of converters was introduced in v2.0 but, again, I'm struggling to understand how to use them effectively. For example, encoding (JSON/form) used to happen via the @FormUrlEncoded()
annotation ... from bumbling around, I guess this has now been replaced with the @FactoryConverter
annotation... is that right? What happens when I need to do both JSON conversion, followed by built_value
conversion? Am I supposed to create a "chain" of converters?
It would be great (at some point) if we could get some documentation on how the new usage patterns should work. I'd be happy to create a PR ... but at the moment, I am not sure myself :)
help or example needed : how to increase timeout in chopper ?
Hi there,
I'm currently using this library in a Flutter project and I am working with an API with explicit trailing slashes in the endpoints (it's a security feature not a bug).
I noticed that the Request's url automatically truncates the trailing slash of any url.
Is it possible to not do this? The current workaround I have for this is to explicitly put two trailing slashes.
I end up with a setup like this:
@ChopperApi('HomeService')
abstract class HomeServiceDefinition {
@Get(url: 'user/widgets//')
Future<Response> getProfitCenters();
@Get(url: 'user/otherwidgets//')
Future<Response> getUsers();
}
instead of:
@ChopperApi('HomeService')
abstract class HomeServiceDefinition {
@Get(url: 'user/widgets/')
Future<Response> getProfitCenters();
@Get(url: 'user/otherwidgets/')
Future<Response> getUsers();
}
If I declare a service that looks like:
@ChopperApi()
abstract class MyService extends ChopperService {
static MyService create([ChopperClient client]) => _$MyService(client);
@Post(url: 'my/url')
@FormUrlEncoded()
Future<Response<MyInfo>> fetchSomething(@Body() Map<String, dynamic> body);
}
I get an error saying Local variable 'body' can't be referenced before it is declared.
The underlying reason appears to be that the generated code has a local variable called body
and we end up with generated code that looks like:
final body = body;
...
I can easily work around it by renaming the fetchSomething
parameter to be called something other than body
.
my pubspec :
chopper: ^2.1.0 # network api call like retrofit
flutter_advanced_networkimage: ^0.4.13 #image fetch like glide or picasso
http: ^0.11.0
Because chopper 2.1.0 depends on http ^0.11.0 and no versions of chopper match >2.1.0 <3.0.0, chopper ^2.1.0 requires http ^0.11.0.
So, because flutter_app depends on both http 0.12.0 and chopper ^2.1.0, version solving failed.
Because flutter_app depends on flutter_advanced_networkimage ^0.4.13 which depends on http ^0.12.0, http ^0.12.0 is required.
So, because flutter_app depends on http ^0.11.0, version solving failed.
I wanted to write an interceptor that would dump out a curl
command for each Request
. However, the request
object that is passed to the interceptor doesn't have everything it needs.
For example, it doesn't contain the baseUrl
and none of the "body" processing logic has been performed. In theory, the interceptor could call toHttpRequest(...)
however a) it doesn't have the input params it needs, and b) that is leaking out internal knowledge of how ChopperClient.send()
works.
One possibility is to change the Interceptor API so that it takes the output of req.toHttpRequest(...)
. This would be a breaking change however.
Of course, there's a chance that I've missed a different way of being able to log the contents of a request. Just let me know... :)
I'm happy to contribute my interceptor back to the chopper project to be included as an out-of-the-box helper if you like.
Hello,
i've the following JSON returned by a Post API:
{
"userId": 1,
"username": "Daniele",
"password": null,
"role": 100,
"active": true,
"token": "mytoken",
"creatorId": null,
"creationTimeStamp": "2019-06-05T10:11:49"
}
I've created the following 'User' model:
import 'dart:convert';
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart';
@JsonSerializable()
class User {
String username;
String password;
String token;
int userId;
bool active;
int role;
int creatorId;
String creationTimeStamp;
User({this.username, this.password});
Map<String, dynamic> toJson() => _$UserToJson(this);
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
factory User.fromJsonString(String json) => _$UserFromJson(jsonDecode(json));
}
Also, I've created the following ApiService:
import 'dart:async';
import 'package:chopper/chopper.dart';
import 'package:domoweb/src/models/user.dart';
part 'api_service.chopper.dart';
@ChopperApi(baseUrl: '')
abstract class ApiService extends ChopperService {
static ApiService create([ChopperClient client]) => _$ApiService(client);
@Post(path: '/api/Auth/login')
Future<Response<User>> login(@Body() User user);
}
When i call the login method it's not working.
Response<User> resp = await _api.login(User(username: 'myusername', password: 'mypassword'));
The error response message is: Type 'Response' should be 'Response' to implement expected type 'Response'.
The content of pubspec.yaml is:
environment:
sdk: '>=2.2.0 <3.0.0'
dependencies:
angular: ^5.3.0
angular_components: ^0.11.0
chopper: ^2.3.0
json_annotation:
dev_dependencies:
angular_test: ^2.2.0
build_runner: ^1.1.2
build_test: ^0.10.3
build_web_compilers: ^2.0.0
pedantic: ^1.0.0
test: ^1.5.1
chopper_generator: ^2.2.0
analyzer:
json_serializable:
I've already searched on other opened/closed issues but i've not solved my problem.
Can someone help me?
I am Android developer. It is glad to see this project. Will it support Rx pattern? Also, capture common errors like network unavailable, status code not 200 and throw exception.
If supported Rx pattern, assume to like it
||
V
import "dart:async";
import 'package:chopper/chopper.dart';
part "my_service.chopper.dart";
@ChopperApi(baseUrl: "/resources")
abstract class MyService extends ChopperService {
static MyService create([ChopperClient client]) => _$MyService(client);
@Get(url: "{id}")
Observable<Response> getResource(@Path() String id);
@Get(headers: const {"foo": "bar"})
Observable<Response<Map>> getMapResource(@Query() String id);
@Post(url: 'multi')
@multipart
Observable<Response> postResources(
@Part('1') Map a,
@Part('2') Map b,
@Part('3') String c,
);
@Post(url: 'file')
@multipart
Observable<Response> postFile(
@FileField('file') List<int> bytes,
);
}
With the current code of the generator it is not possible to avoid having a trailing slash if for instance a get request is called on the base url of the server.
It would be nice if a trailing slash is only used when explicitly given.
Error on line 17, column 22 of pubspec.yaml: Mapping values are not allowed here. Did you miss a colon earlier?
chopper_generator: ^0.0.1
description: A new Flutter application.
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.2
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^0.8.0
chopper_generator: ^0.0.1
uses-material-design: true
assets:
- images/background.png
I have the following method in a ChopperService:
@Post(path: "authenticate")
Future<Response<Token>> authenticate(@Body() Authenticate authenticate);
Token class is as follow:
part 'user.g.dart';
@JsonSerializable()
class Token extends Jsonable<Token>{
@JsonKey(name: "id_token") String token;
Token();
@override
Map<String, dynamic> toJson() => _$TokenToJson(this);
factory Token.fromJson(Map<String, dynamic> json) => _$TokenFromJson(json);
factory Token.fromJsonString(String json) => _$TokenFromJson(jsonDecode(json));
}
When I call mySerive.authenticate(data) the following exception raises:
Unhandled exception:
type 'Future<Response<dynamic>>' is not a subtype of type 'FutureOr<Response<Token>>'
ChopperClient.send (package:.........)
_AsyncAwaitCompleter.start (dart:async/runtime/libasync_patch.dart:49:6)
............
I'm currently using JsonConverter() as the converter for ChopperClient...
Nevertheless if I remove the <Token>
as:
@Post(path: "authenticate")
Future<Response> authenticate(@Body() Authenticate authenticate);
Then the service runs fine and returns a Map with the {id_token: eyJhbGciOiJIUzUxMiJ9.ey.......}
So what can I do to get the Token as the body of my Response???
Is JSON Web Tokens (JWT) authentication supported? I have been trying to authenticate to a JHipster server using JWT authentication and I am getting the exception:
Unhandled exception: Instance of 'Response' #0 ChopperClient.send (package:chopper/src/base.dart:161:7) <asynchronous suspension> #1 _$UserChopperApi.authenticate..............
I am currently able to get a response from endpoints that doesn't require an authorization token, but from endpoints that requires authorization bearer token in the headers I need to authenticate first.
I tried a classic http.post(url, body, headers) to authenticate and works good, but I really want to use chopper for all my API calls.
If anyone could provide an example.
Note: I am new with chopper and dart.
Thanks in advance
Hi,
I'm using chopper to communicate with API by using POST. This is my request:
@Post(path: "/")
Future<Response<String>> getData(@Body() RequestParam request);
How to correct serialize RequestParam to Map<String, dynamic>? I'm using built_value to serializer.
when sending @Field("preferences[]") List<int> preferences
chopper sends:
{ "preferences[]":[2000008,2000149] }
retrofit sends:
{ "preferences[]":2000008, "preferences[]":2000149 }
in my app, I depend on this default behavior
take a look at: https://stackoverflow.com/questions/53832007/how-to-send-liststring-with-retrofit
Chopper in Dart packages shows that Chopper using a MIT based license.
Could you please add a LICENSE file to this repo?
Thanks.
@Post(url: "api")
@FormUrlEncoded()
Future<Response<SomeResponse> postRequest({@field("param1") int param1});
When defined a interface using int or other types which is not a String, there may cause a cast exception in request.dart
because:
baseRequest.bodyFields = (body as Map).cast<String, String>();
final client = ChopperClient();
client.get(url);
client.post(url, body: body, ...);
morning @lejard-h ... this may be more of a question than an issue... I noticed that the dev
branch is now building to cache
, rather than source
.
When I generate code now, my app won't compile. I did a bit of digging and I believe this is a known problem in the flutter world (flutter/flutter#16603)... I'm curious why this change was made. I've confirmed that changing it back to source
gets everything working again.
I have two response (single and array like)
{
"type": "topic",
"data": {
"_id": "5c6d2bccf0059484c041993c",
"name": "Demo topic 1"
}
}
{
"type": "topics",
"data": [
{
"_id": "5c6d2bccf0059484c041993c",
"name": "Demo topic 1"
},
{
"_id": "5c6d5bec115be334f18f8c48",
"name": "Demo topic 2"
}
],
"meta": {
"total": 200
}
}
If a client concurrently submit a request to the server multiple time via a ChopperClient, every response the client receives is the last one. Is this a designed feature just like the comment in the _StreamControllerAddStreamState says?
class _StreamControllerAddStreamState extends _AddStreamState {
// The subscription or pending data of a _StreamController.
// Stored here because we reuse the _varData
field in the _StreamController
// to store this state object.
var varData;
..
How can I define an endpoint with a dynamic query mapper which builds url requests like: http://my.server.com/api/friends?group=coworker&age=42&page=1&size=20&sort=name,asc
without having to declare a @query for each param. Retrofit has QueryMap, I mean something like that
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.