gresau / okapi Goto Github PK
View Code? Open in Web Editor NEWOpenAPI (AKA Swagger) document generation for Rust projects
License: MIT License
OpenAPI (AKA Swagger) document generation for Rust projects
License: MIT License
Thank you for this project! I've been struggling with documenting an API I build, and keeping that documentation up to date. I'm trying to add okapi to my project to have the documentation be created automatically. Unfortunately, I'm running into some issues:
As it stands, Rocket is able to provide neat Rust error messages. Consider the following example:
#![feature(decl_macro, proc_macro_hygiene)]
use rocket::{get, routes};
use rocket_contrib::json::Json;
#[derive(serde::Serialize)]
struct Response {
reply: String,
}
#[get("/")]
fn my_controller() -> Json<Response> {
Json(Response {
reply: 0,
})
}
fn main() {
rocket::ignite()
.mount("/", routes![my_controller])
.launch();
}
This fails with a compile error:
[me@mycomputer tmp]$ cargo check
Checking tmp v0.1.0 (/home/me/code/rust/tmp)
error[E0308]: mismatched types
--> src/main.rs:17:16
|
17 | reply: 0,
| ^
| |
| expected struct `std::string::String`, found integer
| help: try using a conversion method: `0.to_string()`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0308`.
error: could not compile `tmp`.
However, when the code is changed to use Okapi:
#![feature(decl_macro, proc_macro_hygiene)]
use rocket::get;
use rocket_contrib::json::Json;
use rocket_okapi::{openapi, routes_with_openapi};
use schemars::JsonSchema;
#[derive(serde::Serialize, JsonSchema)]
struct Response {
reply: String,
}
#[openapi]
#[get("/")]
fn my_controller() -> Json<Response> {
Json(Response {
reply: 0,
})
}
fn main() {
rocket::ignite()
.mount("/", routes_with_openapi![my_controller])
.launch();
}
The error message changes:
[me@mycomputer tmp]$ cargo check
Checking tmp v0.1.0 (/home/me/code/rust/tmp)
error[E0308]: mismatched types
error: aborting due to previous error
For more information about this error, try `rustc --explain E0308`.
error: could not compile `tmp`.
The useful information about what caused the compilation error is gone, making it really hard to debug. Is there any way to get it back?
When i generate rustdoc for my project, I find okapi_add_operation_for_ functions in the documentation, which is a bit confusing since these are not visible in the project source anywhere (since I assume they are derived from the okapi macros).
I assume it would be good if these functions could be generated with #[doc(hidden)] somehow...
With the new nightly compiler I get the following warning.
warning: the feature `specialization` is incomplete and may not be safe to use and/or cause compiler crashes
--> rocket_okapi/src/lib.rs:1:12
|
1 | #![feature(specialization)]
| ^^^^^^^^^^^^^^
|
= note: `#[warn(incomplete_features)]` on by default
= note: see issue #31844 <https://github.com/rust-lang/rust/issues/31844> for more information
Referring to this line:
Line 1 in 86da308
I have not looked into this much and everything seems to work fine, but just so you know.
I am quite new to rust and found that default parameter is not allowed in rust.
but I could not find a way to set a default value for a parameter in the swagger documentation page.
How can I set the default value for like page = 1
in such codes?
#[openapi(tag = "Customer")]
#[get("/customer?<limit>&<page>")]
pub async fn get_customers(
db: &State<Database>,
limit: i64,
page: Option<i64>,
) -> Result<Json<Vec<Customer>>, Json<MessageResponse>>
Hi, I'm trying to set up rocket_okapi in my project and getting some errors:
// I can't find this instance (rocket_db_pools::Connection<T>)
the trait `OpenApiFromRequest<'_>` is not implemented for `rocket_db_pools::Connection<db::MyDB>`
// There is an instance for &State<T> but T has to have 'static lifetime... I'm not sure how that is possible?
the trait `OpenApiFromRequest<'_>` is not implemented for `&rocket::State<MyState>`
// I can find this instance* but I'm not sure why I'm getting this for MyType, since it derives Deserialize and JsonSchema
// * https://docs.rs/rocket_okapi/0.8.0-rc.1/rocket_okapi/request/trait.OpenApiFromData.html
the trait `OpenApiFromData<'_>` is not implemented for `rocket::serde::json::Json<MyType>`
And this one which is not a missing instance, I think it has to do with using #[macro_use] extern crate rocket;
:
expected enum `rocket_http::method::Method`, found enum `rocket::http::Method`
note: perhaps two different versions of crate `rocket_http` are being used?
Package versions:
schemars = "0.8"
okapi = { version = "0.7.0-rc.1" }
rocket_okapi = { version = "0.8.0-rc.1", features = ["swagger"] }
[dependencies.rocket]
git = "https://github.com/SergioBenitez/Rocket"
version = "0.5.0-rc.1"
features = ["json"]
[dependencies.rocket_db_pools]
git = "https://github.com/SergioBenitez/Rocket"
version = "0.1.0-rc"
features = ["sqlx_postgres"]
Thanks...
I have modified the included example to a version that does not compile:
#![feature(proc_macro_hygiene, decl_macro)]
#[macro_use]
extern crate rocket;
#[macro_use]
extern crate rocket_okapi;
use rocket_contrib::json::Json;
use rocket_okapi::swagger_ui::*;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
struct User {
user_id: u64,
username: String,
#[serde(default)]
email: Option<String>,
}
#[openapi]
#[get("/user/<id>")]
fn get_user(mut id: u64) -> Option<Json<User>> {
Some(Json(User {
user_id: id,
username: "bob".to_owned(),
email: None,
}))
}
#[openapi]
#[post("/user", data = "<user>")]
fn create_user(mut user: Json<User>) -> Json<User> {
user
}
fn main() {
rocket::ignite()
.mount(
"/",
routes_with_openapi![get_user, create_user],
)
.mount(
"/swagger-ui/",
make_swagger_ui(&SwaggerUIConfig {
url: Some("../openapi.json".to_owned()),
urls: None,
}),
)
.launch();
}
This creates a problem here and here.
The problem is that the generated key generated here will include the mut
(for example mut id
instead of id
). The macro assumes the rocket parameter names matches the generated keys exactly, but as explained, the keys might contain mut
.
This gives a very confusing error message:
error: macros that expand to items must be delimited with braces or followed by a semicolon
--> examples/json-web-api/src/main.rs:32:1
|
32 | #[openapi]
| ^^^^^^^^^^
help: change the delimiters to curly braces
|
32 | {[openapi}
| ^ ^
help: add a semicolon
|
32 | #[openapi];
| ^
error: Could not find argument id matching path param.
--> examples/json-web-api/src/main.rs:32:1
|
32 | #[openapi]
| ^^^^^^^^^^
error[E0425]: cannot find function `okapi_add_operation_for_get_user_` in this scope
--> examples/json-web-api/src/main.rs:58:49
|
58 | routes_with_openapi![get_user, create_user],
| ^^^^^^^^ help: a function with a similar name exists: `okapi_add_operation_for_create_user_`
error: aborting due to 3 previous errors
For more information about this error, try `rustc --explain E0425`.
error: Could not compile `json_web_api`.
To learn more, run the command again with --verbose.
It seems that rocket::http::Status
does not implement OpenApiResponder
, so that simple route will fail:
use rocket::http::Status;
#[get("/")]
fn func() -> Status { Status::Ok }
It's quite tricky, because the interface is not supposed to have access to the object itself, only type. As a temporary workaround l4l@9206ab2 may work, though I don't really think it's a good idea to merge it.
Maybe some wrapper should be considered for statuses? Procedural macros cannot properly determine whether the item is particular type or not in general, afaik, although they may emit warnings for a wrapper hinting.
Hey :)
Due to some post on a ticket (which I currently do not find) I tried to implement this on one of my projects (which contains one of the special types things for rocket).
Now I have the following endpoint
#[openapi(tag="Orders")]
#[post("/", data = "<data>", format = "application/json")]
pub async fn create_order(
db_state: &State<MongoState>,
data: Json<CreateOrderRequest>,
) -> Result<Json<OrderResponse>, (Status, Json<ErrorMessage>)> {
// some things happen...
}
For that I get the following error:
the trait bound `rocket::serde::json::Json<rust_common::models::rocket::ErrorMessage>: schemars::JsonSchema` is not satisfied
Well ErrorMessage
Isn't a huge thing
#[derive(Serialize, Deserialize, std::fmt::Debug, JsonSchema)]
pub struct ErrorMessage {
pub message: String,
pub code: u16,
}
Am I doing something wrong? Other endpoints with the option of json do work
okapi
does not seem to use unsafe code, which is very good :)
To prevent this from ever happening, add following line to the top:
#![forbid(unsafe_code)]
of https://github.com/GREsau/okapi/blob/master/okapi/src/lib.rs
and https://github.com/GREsau/okapi/blob/master/rocket-okapi-codegen/src/lib.rs
and https://github.com/GREsau/okapi/blob/master/rocket-okapi/src/lib.rs
This will make the package green in
https://github.com/rust-secure-code/cargo-geiger
util.rs โ add_schema_response() became private with this commit. What's the reason doing so?
I used it to implement OpenApiResponder for my types which are serialized to Json in a custom way.
Given a route function which uses rocket::response::content::*
wrappers to specify the returned content type okapi fails bc the responder is not implemented on the wrapper types.
the trait `rocket_okapi::response::OpenApiResponder<'_>` is not implemented for `rocket::response::content::Plain<std::string::String>`
Hey!
First of all: Amazing crate! Thank you very much for this!
I am using request guards to add authentication to my application. This requires the user to add the Authorization header to a number of requests.
I would like to specify this in the API documentation.
An example function can look like this:
#[openapi]
#[get("/get")]
pub fn get_account_information(me: State<Account>, auth: Authenticate) -> Result<Json<AccountInformation>, Failure>
{
me.get(auth.0)
.and_then(|acc_info| Ok(Json(acc_info)))
}
The authentication guard returns 401 if the token does not match any user and adds the header requirement to the request.
With the default settings, unions are rendered using the 'anyOf' relation, which is fine:
TreeModel:
anyOf:
- $ref: '#/components/schemas/ScoreTree'
- $ref: '#/components/schemas/SongTree'
- $ref: '#/components/schemas/PartTree'
- ... etc
But when you want to use the OpenAPI generator to generate client code, the enum declaration needs (at least >= openapi-generator v5.0.0) a 'discriminator' property: https://swagger.io/docs/specification/data-models/inheritance-and-polymorphism/
This seems to correlate with serde's #[serde(tag = "type")]
serialization option for enums.
Edit: seems to be more related to Schemars. Pre-existing issue: GREsau/schemars#39
Hi :)
First of all, thanks for creating and maintaining okapi. It's super helpful!
I stumbled upon a little issue when designing routes for file upload.
In our design, files could be uploaded in raw form as an application/octet-stream
to our route.
As far as I understand, the expected way of doing this in rocket is to use the Data helper.
However in your code-base, there's currently String
set as the type for the stream. One would expecte [u8]
for an octet stream.
#[openapi(tag = "files")]
#[post("/<entity_id>", data = "<data>")]
pub async fn create_file(
entity_id: Uuid,
data: Data<'_>,
...
The generated spec:
"requestBody": {
"content": {
"application/octet-stream": {
"schema": {
"type": "string"
}
}
},
"required": true
},
The expected spec:
"requestBody": {
"content": {
"application/octet-stream": {
"schema": {
"type": "array",
"items": {
"type": "integer",
"format": "uint8",
"minimum": 0.0
}
}
}
},
"required": true
},
This popped up as an issue, when we tried to upload files via our frontend, but the typechecks failed as the schema expected a String
.
Using &[u8]
instead of Data
works fine and crates the correct schema, however that's not how one works with files in Rocket and brings its own ploblems with Rocket's default data limits.
I'm not 100% sure if this is indeed a bug, of if we're just abusing the spec.
I couldn't see if this was supported in the docs, but what I'd like to do it add tags to resources to group them together, as demonstrated on https://swagger.io/docs/specification/grouping-operations-with-tags/.
One possible way to support this would be to add an attribute to openapi
:
#[openapi(tag="example")]
#[get("/")]
fn example() -> status::NoContent
unimplemented!()
}
From there, you could have a trait along the lines of the following:
pub trait OpenApiTag {
fn tag(gen: &mut OpenApiGenerator) -> OpenApiTag;
}
Plus some code to stitch it together as I can't currently see a way for the generator to add tags.
Hi @GREsau,
I'm using okapi in a large project (~200 endpoints), and all of these endpoints need to be documented. Currently, I have separated these endpoints into modules, that each publicly export a function called get_routes
. This function internally uses the routes_with_openapi
macro to generate a Vec<rocket::Route>
, and then the Rocket instance mounts the results of these functions on a separate mount point. The issue with this approach is that this will generate many openapi.json
files instead of one. This in turn does not play well with swagger-codegen
to automatically generate client libraries, and it is unfamiliar for many front enders. In the ideal case, I think we should generate one openapi.json
file containing information about all documented routes in the Rocket instance.
I talked to @ralpha about this, and we both tried to cobble together something basic, but both our approaches hit a dead end. My approach was to create two macros called merge_routes
and mount_routes
, which supersedes the mounting functionality in Rocket. mount_routes
would handle the namespacing that is normally handled by Rocket, and the merge routes would merge together the openapi.json
's that have been created for each group of routes. I had imagined to build something along these lines:
let routes = merge_routes![
mount_routes!["/module1", routes_with_openapi![route1, route2]],
mount_routes!["/module2", routes_with_openapi![route3, route4]],
];
rocket::ignite().mount("/", routes).launch();
I seems a little silly to me however, to redo the work that Rocket is already doing, and to merge the json files instead of maybe generating one already correct file. I was wondering I you had run into this problem yet, and if so, if you had any thoughts on how to solve it?
As stated, it would be very nice if the examples generated by swagger ui would work for Result types by defaulting to the OK case. I would be willing to help with this if I got some pointers
The RemoveRefSiblings
visitor breaks the openapi dart client generator (see OpenAPITools/openapi-generator#9443) and we'd like to remove it when generating our openapi spec.
I know this is already on the list in the README, but I think it would be good to track and discuss this feature here.
I've run into an issue beyond my Rust knowledge when trying to deserialize a SecurityScheme
. This test will repro.
#[test]
fn test_deser_security_scheme() -> Result<(), Box<dyn std::error::Error>> {
let raw = r#"{
"type": "apiKey",
"description": "AWS Signature Version 4 authentication",
"name": "Authorization",
"in": "header",
"x-amazon-apigateway-authtype": "awsSigv4"
}"#;
let deser: openapi3::SecurityScheme = serde_json::from_str(raw)?;
assert_eq!(
deser,
openapi3::SecurityScheme {
schema_type: "apiKey".to_string(),
description: Some("AWS Signature Version 4 authentication".to_string()),
data: openapi3::SecuritySchemeData::ApiKey {
location: "header".to_string(),
name: "Authorization".to_string(),
},
extensions: okapi::Map::new() // should have x-amazon-apigateway-authtype
}
);
Ok(())
}
Deserializing fails with Error: Error("missing field
type", line: 7, column: 5)
. type
is definitely there though. It has to have something to with https://github.com/GREsau/okapi/pull/56/files#diff-fb72987bae6646074514b23cd41d13a4d1b851455b9bf21084a32668fd729196 but I'm not sure what.
When the content type is application/octet-stream, no schema is created for the response, but it is created for the request. In this case, if there is no schema, openapi-generator typescript-fetch throws an error after version 5.3.1.
like this: Undefined property/schema for 'SchemaFor200ResponseBodyApplicationOctetStream'
for request it creates :
"requestBody": {
"content": {
"application/octet-stream": {
"schema": {
"type": "array",
"items": {
"type": "integer",
"format": "uint8",
}
}
}
}
But for response it creates any schema:
"responses": {
"200": {
"content": {
"application/octet-stream": {}
}
},
Hi,
I'm trying to use okapi to generate openapi.yaml, but I'm having problems because the datatypes that I'm trying to use perhaps aren't supported by okapi, or perhaps that I don't understand the way they are supported. I have ended up implementing a lot of Traits and I'm just wondering if I haven't taken a wrong turn and made it harder than it needs to be.
I want to use an endpoint with a Form that contains a file. This is fairly easy in Rocket using the TempFile struct. When I try to mark the endpoint with openapi, I have errors that JsonSchema, Deserialize, Serialize and Default are not implemented for TempFile. In order to implement the Traits, I have to create a local struct for TempFile because you are not allowed to implement a trait you didn't define for a struct you didn't define. Then you need to write code incorporating the functionality from the original struct into your struct. Below are the stubs that I have implemented to satisfy the compiler for now. In the current state, it complains that OpenApiFromData is not implemented for rocket::form::Form<ExampleForm<'_>>. To solve this it looks like I might have to declare a local struct and add all of the traits and functions to it that I need.
Am I going about this the right way?
Here is my code:
#[macro_use] extern crate rocket;
use rocket::serde::json::Json;
use std::path::Path;
use rocket::form::Form;
use rocket_okapi::{ openapi, routes_with_openapi };
use serde::{Deserialize, Serialize};
use schemars::JsonSchema;
use rocket::fs::TempFile;
use schemars::gen::SchemaGenerator;
///////////// This is the code I want to use //////////////////////////////////////////
#[derive(Serialize, Deserialize, FromForm, JsonSchema)]
pub struct Example {
example_id: u64,
}
#[derive(FromForm, JsonSchema, Debug, Default, Serialize, Deserialize)]
pub struct ExampleForm<'f> {
example_data: ExampleTempFile<'f>,
}
#[openapi]
#[post("/example", format = "multipart/form-data", data = "<form>")]
pub async fn post_example(form: Form < ExampleForm < '_ > > ) -> std::result::Result<Json<Example>, String> {
let example = Example {
example_id: 1
};
Ok(Json(example))
}
#[rocket::main]
#[doc = "The main entry point for Rocket" ]
async fn main() -> Result <(), rocket::Error> {
rocket::build()
.mount("/", routes_with_openapi![post_example])
.launch()
.await
}
///////////// This is the supporting code stubs I think I need to fill out //////////
#[derive (Debug)]
struct ExampleTempFile<'a>(&'a mut TempFile<'a>);
impl ExampleTempFile<'_> {
pub async fn persist_to<P>(&mut self, path: P) -> std::io::Result<()>
where P: AsRef<Path>
{
self.0.persist_to(path).await
}
}
impl<'de> Deserialize<'de> for ExampleTempFile<'_> {
fn deserialize<D>(_: D) -> Result<Self, <D as rocket::serde::Deserializer<'de>>::Error> where D: rocket::serde::Deserializer<'de> {
Ok(ExampleTempFile { 0: &mut TempFile::Buffered { content: "" } })
}
}
impl Serialize for ExampleTempFile<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: rocket::serde::Serializer {
serializer.serialize_bool(true)
}
}
impl Default for ExampleTempFile<'_> {
fn default() -> Self {
ExampleTempFile{ 0: &mut TempFile::default().unwrap() }
}
}
impl<'a> schemars::JsonSchema for ExampleTempFile<'a> {
fn schema_name() -> String {
"TempFile".to_owned()
}
fn json_schema( _: &mut SchemaGenerator ) -> schemars::schema::Schema {
schemars::schema::SchemaObject::default().into()
}
}
use rocket::form::FromFormField;
impl<'__f> FromFormField<'_> for ExampleTempFile<'__f> {
}
//////////// This is the bit where it looks like I might have to do the same trick with the local struct again //////////////////
use okapi::Map;
use rocket_okapi::{ gen::OpenApiGenerator, request::OpenApiFromData, Result as OpenApiResult };
use okapi::openapi3::{RequestBody};
impl<'r> OpenApiFromData<'r> for Form<ExampleForm<'r>> {
fn request_body(gen: &mut OpenApiGenerator) -> OpenApiResult <RequestBody>{
Ok(RequestBody {
content: { Map::new() },
required: true,
..Default::default()
})
}
}
Here is my Cargo.toml:
[package]
name = "example"
version = "0.1.0"
edition = "2018"
[dependencies]
rocket = { version = "0.5.0-rc.1", features = [ "json" ] }
serde = { version = "1.0.64", features = ["derive"] }
serde_json = "1.0.64"
log = "0.4.14"
async-global-executor = "2.0.2"
okapi = "0.6.0-alpha-1"
rocket_okapi = "0.7.0-alpha-1"
schemars = "^0.8"
I got it nearly compiling with #29 and the rocket master ;)
But i got still an error on Uuid in parameter:
error[E0277]: the trait bound `rocket_contrib::uuid::Uuid: OpenApiFromParam<'_>` is not satisfied
--> src/api/auth.rs:29:1
|
29 | #[openapi]
| ^^^^^^^^^^ the trait `OpenApiFromParam<'_>` is not implemented for `rocket_contrib::uuid::Uuid`
|
= note: required by `path_parameter`
= note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
When deserializing from json the schemas from a param also get added to the extensions object of a param, resulting in faulty json being outputted when serializing again
use okapi::openapi3::{Operation, RefOr, Parameter};
fn main() {
let json = "{\"responses\": {}, \"name\": \"someOp\", \"parameters\": [{\"in\": \"path\", \"schema\": {\"$ref\": \"#/defid\"}, \"name\": \"param1\"}]}";
let operation: Operation = serde_json::from_str(json).unwrap();
for param in &operation.parameters {
match param {
RefOr::Ref(_) => {
}
RefOr::Object(p) => {
println!("p.json: {}", serde_json::to_string(p).unwrap());
println!("p.value.json: {}", serde_json::to_string(&p.value).unwrap());
println!("p.extensions.json: {}", serde_json::to_string(&p.extensions).unwrap());
}
}
}
println!("json: {}", serde_json::to_string(&operation).unwrap());
}
results in
p.json: {"name":"param1","in":"path","schema":{"$ref":"#/defid"},"schema":{"$ref":"#/defid"}}
p.value.json: {"schema":{"$ref":"#/defid"}}
p.extensions.json: {"schema":{"$ref":"#/defid"}}
json: {"parameters":[{"name":"param1","in":"path","schema":{"$ref":"#/defid"},"schema":{"$ref":"#/defid"}}],"responses":{},"name":"someOp"}
The same happens when deserializing an operation with responses, deserializing and serializing this
"responses": {
"200": {
"description": "info to be returned",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/getInfoResultSchema"
}
}
}
},
"default": {
"description": "Unexpected error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DefaultError"
}
}
}
}
}
results in this
"responses": {
"default": {
"description": "Unexpected error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DefaultError"
}
}
}
},
"200": {
"description": "info to be returned",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/getInfoResultSchema"
}
}
}
},
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/getInfoResultSchema"
}
}
},
"description": "info to be returned"
}
}
I'd like to use Okapi to document the query params in my Rocket Route
. If I do the following:
#[openapi]
#[get("/?<id>")]
fn get_by_id(id: i32) -> String {
format!("got {}", id)
}
then the resulting swagger has an empty Parameters
section. I have tried switching to a form (which admittedly would make more sense because it allows you to add doc comments):
#[openapi]
#[derive(FromForm, JsonSchema)]
struct Query {
/// This is the id that we will format very nicely for you.
id: i32,
}
#[openapi]
#[get("/?<query..>)]
fn get_by_id(query: Query) -> String {
format!("got {}", query.id)
}
This however yields the same results. Are query params not yet supported?
When deserializing from json the schemas from a param also get added to the extensions object of a param, resulting in faulty json being outputted when serializing again (schema attribute is added twice)
use okapi::openapi3::{Operation, RefOr, Parameter};
fn main() {
let json = "{\"responses\": {}, \"name\": \"someOp\", \"parameters\": [{\"in\": \"path\", \"schema\": {\"$ref\": \"#/defid\"}, \"name\": \"param1\"}]}";
let operation: Operation = serde_json::from_str(json).unwrap();
for param in &operation.parameters {
match param {
RefOr::Ref(_) => {
}
RefOr::Object(p) => {
println!("p.json: {}", serde_json::to_string(p).unwrap());
println!("p.value.json: {}", serde_json::to_string(&p.value).unwrap());
println!("p.extensions.json: {}", serde_json::to_string(&p.extensions).unwrap());
}
}
}
println!("json: {}", serde_json::to_string(&operation).unwrap());
}
results in
p.json: {"name":"param1","in":"path","schema":{"$ref":"#/defid"},"schema":{"$ref":"#/defid"}}
p.value.json: {"schema":{"$ref":"#/defid"}}
p.extensions.json: {"schema":{"$ref":"#/defid"}}
json: {"parameters":[{"name":"param1","in":"path","schema":{"$ref":"#/defid"},"schema":{"$ref":"#/defid"}}],"responses":{},"name":"someOp"}
The same happens when deserializing an operation with responses, deserializing and serializing this
"responses": {
"200": {
"description": "info to be returned",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/getInfoResultSchema"
}
}
}
},
"default": {
"description": "Unexpected error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DefaultError"
}
}
}
}
}
results in this
"responses": {
"default": {
"description": "Unexpected error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DefaultError"
}
}
}
},
"200": {
"description": "info to be returned",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/getInfoResultSchema"
}
}
}
},
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/getInfoResultSchema"
}
}
},
"description": "info to be returned"
}
}
Is it possible to implement JsonSchema
for chrono::DateTime
?
I have structs
#[derive(Queryable, QueryableByName, JsonSchema, AsChangeset, Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]
#[table_name = "contents"]
pub struct Content {
pub id: i32,
pub title: String,
pub story: String,
pub published: DateTime<chrono::Utc>,
pub user_id: i32,
}
#[derive(Debug, Insertable, Deserialize, JsonSchema)]
#[serde(crate = "rocket::serde")]
#[table_name = "contents"]
pub struct InsertableContent {
pub title: String,
pub story: String,
#[serde(with = "chrono::serde::ts_seconds")]
pub published: DateTime<chrono::Utc>,
pub user_id: i32,
}
Compiling gives following errors
error[E0573]: expected type, found module `chrono::serde::ts_seconds`
--> src/models.rs:23:20
|
23 | #[serde(with = "chrono::serde::ts_seconds")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ not a type
error[E0277]: the trait bound `chrono::DateTime<chrono::Utc>: rocket_okapi::JsonSchema` is not satisfied
--> src/models.rs:13:20
|
13 | pub published: DateTime<chrono::Utc>,
| ^^^^^^^^^^^^^^^^^^^^^ the trait `rocket_okapi::JsonSchema` is not implemented for `chrono::DateTime<chrono::Utc>`
error[E0277]: the trait bound `chrono::DateTime<chrono::Utc>: rocket_okapi::JsonSchema` is not satisfied
--> src/models.rs:6:38
|
6 | #[derive(Queryable, QueryableByName, JsonSchema, AsChangeset, Serialize, Deserialize)]
| ^^^^^^^^^^ the trait `rocket_okapi::JsonSchema` is not implemented for `chrono::DateTime<chrono::Utc>`
|
note: required by `rocket_okapi::JsonSchema::_schemars_private_is_option`
--> /home/fs0c131y/.local/share/cargo/registry/src/github.com-1ecc6299db9ec823/schemars-0.8.6/src/lib.rs:372:5
|
372 | fn _schemars_private_is_option() -> bool {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: this error originates in the derive macro `JsonSchema` (in Nightly builds, run with -Z macro-backtrace for more info)
Some errors have detailed explanations: E0277, E0573.
For more information about an error, try `rustc --explain E0277`.
error: could not compile `content_service` due to 3 previous errors
Can query string parameters be described? If not, how much work would it be to add support for them?
Hello! I am new to Rust and have recently learned about okapi. I am trying to use it for a REST API with a mongodb backend. I would like to properly handle and report mongodb-related failures as a HTTP 500 error, but I cannot figure out how to do that. I suppose I could implement OpenApiResponder for the mongodb errors, but it is not clear to me how to properly implement or how I would set up the return type for my handler. An example or more documentation would be very helpful =)
When attempting to specify two routes with the same path and method, but different accepted format (e.g. format = "application/json"
vs format = "application/cbor"
) error message appears: An OpenAPI operation has already been added for ...
The reason is that currently only method and path are taken into account when adding a new routes, while format
should be considered as well.
(Thanks a lot for a nice crate!)
When two routes have the same name for a Request / Response the namespaces are clobbered. Example:
https://github.com/leeola/example_okapi_name_collision (specifically this file) will incorrectly report that the /two
route has a response type of:
{
"reply": "string"
}
Instead of what it should be,
{
"different_schema": "string"
}
I'm happy to help developing a solution to this, but I'm unsure how best it can be handled. Ideally, I imagine something resembling idiomatic OpenAPI specs should be generated. Though, looking at the classic PetStore example it looks like that entire bottom area is ripe for having many structure names occupy the same namespace.
Thoughts on how best to handle this?
Please create a new release of okapi/rocket_okapi because the current one still tries to use the old 0.1.x schemars.
If you update a dependency you should bump the version number and publish it on crates.io.
Please :)
Reading through the code, it seems like okapi only implements the aforementioned status codes in response schema generation. (https://github.com/GREsau/okapi/blob/master/rocket-okapi/src/response/responder_impls.rs ). Are there plans / interest to add full support for the entire range of possible status messages (e.g. probably adding a responder impl for rocket::http::Status.
I tried implementing something but my macro skills aren't really up to par, and it isn't compiling. (master...xylix:extend-status-code-support )
I could also be misreading Rocket's status classes, but am unsure in what way.
It seems that route docs doesn't currently used to generate route descriptions.
Am I missing anything?
Thanks for the project!
I have an api with a parameter async. This is a reserved word, so to handle this I have the following form defined, with the form field statement specifying that the variable is_async is called "async" in the API.
#[derive(FromForm, JsonSchema, Debug)]
struct MoveInput {
to: String,
#[form(field = "async")]
is_async: Option<bool>
}
#[openapi]
#[post("/move/?<input..>")]
fn mv(input: Form<MoveInput>, session: State<Session>) {
in the swagger UI this is still shown as is_async.
Hi,
Thank you so much for this library! It really is quite impressive what you have implemented thus far!
This issue is alluded to in #52 but I have observed that this seems to be a larger issue impacting all of the typed streams in rocket 0.5.0-rc.1
. I hit the same issue mentioned in #52 but I saw that OpenApiResponderInner
had already been implemented for TextStream<S>
, ReaderStream<S>
, and ByteStream<S
so I decided to see if TextStream
would work for my use case. The result is the same compiler error encountered with EventStream!
:
error[E0562]: `impl Trait` not allowed outside of function and inherent method return types
The EventStream!
and other typed streams return types ultimately expand to a type like the following and they trip up the openapi
macro because it can't just reuse the type as defined.
impl rocket::futures::stream::Stream<Item = rocket::response::stream::Event>
I pulled your latest code and dug around a bit and made an attempt at a fix but I didn't see a clear path forward. In the case of TextStream
and ByteStream
, I could see where the openapi
macro could rewrite the type to one of the string or byte representations that already have an implementation of OpenApiResponderInner
. For EventStream
, I was thinking along the same lines and tried rewriting the type as the rocket Event
type; however, this type doesn't implement rocket::response::Responder
so that looked like a dead end.
One alternative I was thinking might be possible would be to add an optional field to the openapi
attribute that allows the developer to specify a type to substitute in place of the type that the typed stream macros expand to. I obviously don't have the same insight into the inner workings so this may be a terrible idea!
If you have some guidance on how you would like to see this functionality implemented I would be glad to take on this effort!
Thanks!
Hi,
First of all thanks for contributing okapi
to the world!
I just tried to use the first EventStream example from the Rocket documentation decorated with #[openapi]
and ran into the following error:
cargo build
Compiling example v0.1.0 (...)
error[E0562]: `impl Trait` not allowed outside of function and inherent method return types
--> src/main.rs:54:16
|
54 | fn stream() -> EventStream![] {
| ^^^^^^^^^^^^^^
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
It compiles just fine when I use #[openapi(skip)]
instead of #[openapi]
. But do you have any idea how to make the example work with #[openapi]
? Any help would be highly appreciated!
Feature request:
Add support for AsyncAPI as well as OpenAPI. This would allow WebSocket based APIs to be documentable as well. These standards complement each other, and from what I can tell, they mimic each other, but they don't overlap in functionality other than maybe type schemas, which should have the same model in both standards.
I have a function where I take an input path like this:
#[openapi]
#[get("/<path..>")]
fn info(path: PathBuf, session: State<Session>) -> String {
format!("info {:?}", path)
}
In the swagger ui, that is displayed as
So there is no parameter listed.
Note, this is with the following versions:
rocket = "0.4.7"
rocket_okapi = "0.6.0-alpha-1"
schemars = "0.7"
okapi = { version = "0.5.0-alpha-1", features = ["derive_json_schema"] }
Where a route is declared as a pub fn
, Rocket creates a handlers that is also declared as pub fn
. Currently, marking a route with #[openapi]
sets the route to private, making it impossible to generate documentation for routes that exists in another module.
(This is an issue for when people have this issue in the future)
I'm getting the warning:
WARN :okapi::merge - Found conflicting responses keys while merging, they have the same name but different values for `401`:
Object(Response { .... })
Object(Response { .... })
How do I resolve this?
Hi !
Rocket has released 0.5 in RC-1, can you support it ?
I had to disable okapi because Rocket 0.5 support stable channel of Rust and using actual version of this crate supports only previous Rocket and so it requires rust nightly
See my migration where I had to disable okapi: mountains/permafrost@e7b5476
Edit: migration done with success: mountains/permafrost@4d53f99
I was thinking about an annotation on the data type maybe.
Currently there is no easy way to get the OpenApi object or the generated file without starting rocket.
I made a small (hacky) workaround to get the OpenApi
object without starting rocket.
Here are some code snippets if someone has the same issue. But it would be code if this was supported in the crate.
Somewhere in my code:
pub fn get_openapi() -> OpenApi{
generate_openapi_file![
//...add all calls that are also inside `routes_with_openapi`..
]
}
Rocket_okapi_codegen/src/lib.rs
#[proc_macro]
pub fn generate_openapi_file(input: TokenStream) -> TokenStream {
routes_with_openapi::parse_openapi_file(input)
}
Rocket_okapi_codegen/src/routes_with_openapi/mod.rs
pub fn parse_openapi_file(routes: TokenStream) -> TokenStream {
get_openapi_file(routes)
.unwrap_or_else(|e| e.to_compile_error())
.into()
}
// Basically a modified `parse_inner` with some small changes
// (there is some custom code like `get_new_openapi_object` here)
fn get_openapi_file(routes: TokenStream) -> Result<TokenStream2>{
let paths = <Punctuated<Path, Comma>>::parse_terminated.parse(routes)?;
let add_operations = create_add_operations(paths)?;
let openapi_object = get_new_openapi_object();
Ok(quote! {
{
let settings = ::rocket_okapi::settings::OpenApiSettings::new();
let mut gen = ::rocket_okapi::gen::OpenApiGenerator::new(settings.clone());
#add_operations
#openapi_object
// It does not return the routes here, but the spec instead.
spec
}
})
}
Like I said this is a bit of a hack. But a native way would be nice.
I am trying to use okapi with a custom Responder for a Json<> wrapped struct. I am getting this error:
Compiling openapi-responder v0.1.0 (/home/richard/small_test_projects/openapi_responder)
error[E0277]: the trait bound `rocket::serde::json::Json<Example>: JsonSchema` is not satisfied
--> src/main.rs:20:12
|
20 | inner: Json<Example>,
| ^^^^^^^^^^^^^ the trait `JsonSchema` is not implemented for `rocket::serde::json::Json<Example>`
|
= note: required by `add_schema_as_property`
Here is my Cargo.toml:
[package]
name = "openapi-responder"
version = "0.1.0"
edition = "2018"
[dependencies]
rocket = { version = "0.5.0-rc.1", features = [ "json" ] }
serde = { version = "1.0.64", features = ["derive"] }
serde_json = { version = "1.0.64" }
okapi = "0.6.0-alpha-1"
rocket_okapi = "0.7.0-alpha-1"
rocket_okapi_codegen = "0.7.0-alpha-1"
schemars = "^0.8"
Here is my main.rs:
#[macro_use]
extern crate rocket;
extern crate rocket_okapi;
use rocket_okapi::{openapi, routes_with_openapi, OpenApiError, response::OpenApiResponderInner, gen::OpenApiGenerator, util::add_schema_response};
use okapi::openapi3::Responses;
use rocket::serde::json::Json;
use serde::{Deserialize, Serialize};
use schemars::JsonSchema;
#[derive(Serialize, Deserialize, JsonSchema)]
pub struct Example {
error: String,
}
#[derive(Responder, JsonSchema)]
#[response(content_type = "json", status=200)]
pub struct ExampleResponse {
inner: Json<Example>,
}
#[openapi]
#[get("/example_response", format="application/json")]
pub async fn get_example_response() -> ExampleResponse {
ExampleResponse { inner: Json(Example { error: "hi".to_owned() } ) }
}
impl OpenApiResponderInner for ExampleResponse {
fn responses(gen: &mut OpenApiGenerator) -> Result<Responses, OpenApiError>{
let mut responses = Responses::default();
let schema = gen.json_schema::<Json<Example>>();
add_schema_response(&mut responses, 200, "application/json", schema)?;
Ok(responses)
}
}
#[rocket::main]
#[doc = "The main entry point for Rocket" ]
async fn main() -> Result <(), rocket::Error> {
rocket::build()
.mount("/", routes_with_openapi![get_example_response])
.launch()
.await
}
I don't understand how I can generate the schema for a Json struct. I can't use the derive(JsonSchema) attribute because it's not a struct, enum or union.
When writing a closure with no arguments, Okapi emits a warning on compilation.
#[openapi]
#[get("/?<id>")]
fn get_by_id(id: Option<i32>) -> String {
let default = || 1;
let id = id.unwrap_or_else(default);
format!("got {}", id)
}
This gives the following result:
warning: unnecessary parentheses around type
--> src/main.rs:16:1
|
16 | #[get("/?<id>")]
| ^^^^^^^^^^^^^^^^ help: remove these parentheses
|
= note: `#[warn(unused_parens)]` on by default
warning: 1 warning emitted
I've looked at the emitted code using cargo expand
, but i couldn't figure out the cause of this warning.
Hi. There is problem with deserializing and then serializing security scheme into JSON. I have a code which parse spec from YAML, then attempt to serialize it into both YAML and JSON. YAML is valid, but JSON produces duplicated fields type
and scheme
:
use okapi::openapi3::OpenApi;
static SPEC_ROOT: &str = r##"openapi: 3.0.3
info:
title: CARDI API
version: 0.14.0
paths: {}
security:
- BasicAuth: []
components:
securitySchemes:
BasicAuth:
type: http
scheme: basic
"##;
let mut spec: OpenApi = serde_yaml::from_str(SPEC_ROOT).unwrap();
println!(
"DEBUG: {:#?}\nJSON: {}\nYAML: {}",
spec,
serde_json::to_string_pretty(&spec).unwrap(),
serde_yaml::to_string(&spec).unwrap()
);
Problem is that fields type
and scheme
ends up in both data
and extensions
fields. Since it looks like some serde limitation/bug, proper solution could be custom deserialization. If this approach is OK, I could try to implement it.
I believe this might be related to #74.
DEBUG: OpenApi {
openapi: "3.0.3",
info: Info {
title: "CARDI API",
description: None,
terms_of_service: None,
contact: None,
license: None,
version: "0.14.0",
extensions: {},
},
servers: [],
paths: {},
components: Some(
Components {
schemas: {},
responses: {},
parameters: {},
examples: {},
request_bodies: {},
headers: {},
security_schemes: {
"BasicAuth": Object(
SecurityScheme {
description: None,
data: Http {
scheme: "basic",
bearer_format: None,
},
extensions: {
"scheme": String(
"basic",
),
"type": String(
"http",
),
},
},
),
},
links: {},
callbacks: {},
extensions: {},
},
),
security: [
{
"BasicAuth": [],
},
],
tags: [],
external_docs: None,
extensions: {},
}
{
"openapi": "3.0.3",
"info": {
"title": "CARDI API",
"version": "0.14.0"
},
"paths": {},
"components": {
"securitySchemes": {
"BasicAuth": {
"type": "http",
"scheme": "basic",
"scheme": "basic",
"type": "http"
}
}
},
"security": [
{
"BasicAuth": []
}
]
}
---
openapi: 3.0.3
info:
title: CARDI API
version: 0.14.0
paths: {}
components:
securitySchemes:
BasicAuth:
scheme: basic
type: http
security:
- BasicAuth: []
I'm trying to setup okapi for rocket 0.5.0-rc.1.
My Cargo.toml file is the following:
[package]
name = "food-organizer"
version = "0.1.0"
edition = "2018"
[dependencies]
# Env
dotenv = "0.15.0"
# Web server
rocket = { version = "0.5.0-rc.1", features=["tls", "json"] }
rocket_cors = "0.5.2"
# API documentation
okapi = "0.7.0-alpha-1"
rocket_okapi = { version = "0.8.0-alpha-1"}
schemars = { version = "0.8.6", features = ["preserve_order"] }
# Scraper
reqwest = "0.11.4"
scraper = "0.12.0"
# Serialisation
serde = "1.0.130"
serde_json = "1.0.68"
# Utils
regex = "1.5.4"
jsonwebtoken = "7.2.0"
chrono = "0.4.19"
bcrypt = "0.10.1"
# OpenSSL
openssl = {version="0.10.36", features=["vendored"]}
# DB driver
diesel = { version = "1.4.8", features = ["sqlite"] }
[patch.crates-io]
rocket = { git = 'https://github.com/SergioBenitez/Rocket', branch = "v0.5-rc" }
rocket_cors = { git = "https://github.com/lawliet89/rocket_cors.git", branch = "master" }
rocket_okapi = { git = "https://github.com/GREsau/okapi", branch = "master" }
okapi = { git = "https://github.com/GREsau/okapi", branch = "master" }
I'm trying to build and I get the same error again and again, like if okapi wasn't using rocket 0.5 :
Compiling rocket_okapi v0.8.0-alpha-1 (https://github.com/GREsau/okapi?branch=master#867e5061)
error[E0432]: unresolved import `rocket::response::content::Custom`
--> /home/alexandre/.cargo/git/checkouts/okapi-727e9b4b9c217cbb/867e506/rocket-okapi/src/handlers/content.rs:2:24
|
2 | use rocket::response::{content::Custom, Responder};
| ^^^^^^^^^^^^^^^ no `Custom` in `response::content`
error[E0432]: unresolved import `rocket::response::content::Custom`
--> /home/alexandre/.cargo/git/checkouts/okapi-727e9b4b9c217cbb/867e506/rocket-okapi/src/handlers/openapi.rs:3:5
|
3 | use rocket::response::content::Custom;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no `Custom` in `response::content`
error[E0412]: cannot find type `Css` in module `rocket::response::content`
--> /home/alexandre/.cargo/git/checkouts/okapi-727e9b4b9c217cbb/867e506/rocket-okapi/src/response/responder_impls.rs:217:27
|
217 | response_content_wrapper!(Css, "text/css");
| ^^^ not found in `rocket::response::content`
error[E0412]: cannot find type `Custom` in module `rocket::response::content`
--> /home/alexandre/.cargo/git/checkouts/okapi-727e9b4b9c217cbb/867e506/rocket-okapi/src/response/responder_impls.rs:218:27
|
218 | response_content_wrapper!(Custom, "*/*");
| ^^^^^^ not found in `rocket::response::content`
|
help: consider importing this struct
|
1 | use rocket::response::status::Custom;
|
error[E0412]: cannot find type `Html` in module `rocket::response::content`
--> /home/alexandre/.cargo/git/checkouts/okapi-727e9b4b9c217cbb/867e506/rocket-okapi/src/response/responder_impls.rs:219:27
|
219 | response_content_wrapper!(Html, "text/html");
| ^^^^ not found in `rocket::response::content`
error[E0412]: cannot find type `JavaScript` in module `rocket::response::content`
--> /home/alexandre/.cargo/git/checkouts/okapi-727e9b4b9c217cbb/867e506/rocket-okapi/src/response/responder_impls.rs:220:27
|
220 | response_content_wrapper!(JavaScript, "application/javascript");
| ^^^^^^^^^^ help: a struct with a similar name exists: `RawJavaScript`
|
::: /home/alexandre/.cargo/git/checkouts/rocket-8bf16d9ca7e90bdc/e382bc5/core/lib/src/response/content.rs:69:1
|
69 | / ctrs! {
70 | | // FIXME: Add a note that this is _not_ `serde::Json`.
71 | | RawJson: JSON, "JSON", "application/json",
72 | | RawXml: XML, "XML", "text/xml",
... |
77 | | RawJavaScript: JavaScript, "JavaScript", "application/javascript"
78 | | }
| |_- similarly named struct `RawJavaScript` defined here
error[E0412]: cannot find type `Json` in module `rocket::response::content`
--> /home/alexandre/.cargo/git/checkouts/okapi-727e9b4b9c217cbb/867e506/rocket-okapi/src/response/responder_impls.rs:221:27
|
221 | response_content_wrapper!(Json, "application/json");
| ^^^^ not found in `rocket::response::content`
|
help: consider importing one of these items
|
1 | use crate::request::from_data_impls::Json;
|
1 | use rocket::serde::json::Json;
|
error[E0412]: cannot find type `MsgPack` in module `rocket::response::content`
--> /home/alexandre/.cargo/git/checkouts/okapi-727e9b4b9c217cbb/867e506/rocket-okapi/src/response/responder_impls.rs:222:27
|
222 | response_content_wrapper!(MsgPack, "application/msgpack");
| ^^^^^^^ not found in `rocket::response::content`
error[E0412]: cannot find type `Plain` in module `rocket::response::content`
--> /home/alexandre/.cargo/git/checkouts/okapi-727e9b4b9c217cbb/867e506/rocket-okapi/src/response/responder_impls.rs:223:27
|
223 | response_content_wrapper!(Plain, "text/plain");
| ^^^^^ not found in `rocket::response::content`
error[E0412]: cannot find type `Xml` in module `rocket::response::content`
--> /home/alexandre/.cargo/git/checkouts/okapi-727e9b4b9c217cbb/867e506/rocket-okapi/src/response/responder_impls.rs:224:27
|
224 | response_content_wrapper!(Xml, "text/xml");
| ^^^ not found in `rocket::response::content`
Some errors have detailed explanations: E0412, E0432.
For more information about an error, try `rustc --explain E0412`.
error: could not compile `rocket_okapi` due to 10 previous errors
Does anyone have an idea about how I can make this work?
Given the following handler:
#[openapi(tag = "users")]
#[get("/?<page>&<sort_key>&<sort_direction>")]
pub async fn list_users(
page: Option<usize>,
sort_key: Option<UserSortKey>,
sort_direction: Option<SortDirection>,
) -> Result<Json<Vec<User>>, AppError> { }
The resulting spec contains the following parameters
array:
"parameters": [
{
"name": "page",
"in": "query",
"schema": {
"type": "integer",
"format": "uint",
"minimum": 0.0
}
},
{
"name": "SortKey",
"in": "query",
"schema": {
"$ref": "#/components/schemas/UserSortKey"
}
},
{
"name": "sortDirection",
"in": "query",
"schema": {
"$ref": "#/components/schemas/SortDirection"
}
}
],
The name
s in the spec are all written in camelCase, however rocket expects the keys of the query params to be written in snake_case.
Can we adjust okapi to mirror the format rocket expects?
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.