├── opg
├── LICENSE
├── README.md
├── crates-io.md
├── Cargo.toml
└── src
│ ├── lib.rs
│ └── macros.rs
├── opg_derive
├── LICENSE
├── README.md
├── crates-io.md
├── Cargo.toml
└── src
│ ├── lib.rs
│ ├── dummy.rs
│ ├── parsing_context.rs
│ ├── symbol.rs
│ ├── bound.rs
│ ├── ast.rs
│ ├── case.rs
│ ├── attr.rs
│ └── opg.rs
├── .gitignore
├── Cargo.toml
├── test_suite
├── Cargo.toml
└── tests
│ ├── repr.rs
│ ├── api.rs
│ └── models.rs
├── .github
└── workflows
│ └── master.yml
├── LICENSE
└── README.md
/opg/LICENSE:
--------------------------------------------------------------------------------
1 | ../LICENSE
--------------------------------------------------------------------------------
/opg/README.md:
--------------------------------------------------------------------------------
1 | ../README.md
--------------------------------------------------------------------------------
/opg/crates-io.md:
--------------------------------------------------------------------------------
1 | ../README.md
--------------------------------------------------------------------------------
/opg_derive/LICENSE:
--------------------------------------------------------------------------------
1 | ../LICENSE
--------------------------------------------------------------------------------
/opg_derive/README.md:
--------------------------------------------------------------------------------
1 | ../README.md
--------------------------------------------------------------------------------
/opg_derive/crates-io.md:
--------------------------------------------------------------------------------
1 | ../README.md
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | .idea
3 | Cargo.lock
4 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | members = ["opg", "opg_derive", "test_suite"]
3 |
--------------------------------------------------------------------------------
/test_suite/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "opg_test_suite"
3 | version = "0.0.0"
4 | authors = ["Ivan Kalinin "]
5 | edition = "2018"
6 |
7 | [dependencies]
8 | opg = { path = "../opg", features = [ "uuid", "chrono" ] }
9 |
10 | [dev-dependencies]
11 | chrono = { version = "0.4", features = ["serde"] }
12 | uuid = { version = "*" }
13 | serde = "1.0"
14 | serde_yaml = "0.8"
15 | serde_repr = "0.1"
16 |
--------------------------------------------------------------------------------
/opg_derive/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "opg_derive"
3 | description = "Rust OpenAPI 3.0 docs generator"
4 | authors = ["Ivan Kalinin "]
5 | license = "Apache-2.0"
6 | version = "0.1.0"
7 | repository = "https://github.com/Rexagon/opg"
8 | keywords = ["openapi", "documentation", "generator"]
9 | categories = ["encoding"]
10 | include = ["src/**/*.rs", "README.md", "LICENSE"]
11 | edition = "2018"
12 |
13 | [lib]
14 | proc-macro = true
15 |
16 | [dependencies]
17 | syn = { version = "1.0", features = ["visit"] }
18 | quote = "1.0"
19 | proc-macro2 = "1.0"
20 | either = "1.5"
21 |
22 | [dev-dependencies]
23 | opg = { version = "0.2", path = "../opg" }
24 |
25 | [package.metadata.docs.rs]
26 | targets = ["x86_64-unknown-linux-gnu"]
27 |
--------------------------------------------------------------------------------
/opg_derive/src/lib.rs:
--------------------------------------------------------------------------------
1 | extern crate proc_macro2;
2 | extern crate quote;
3 | extern crate syn;
4 |
5 | mod ast;
6 | mod attr;
7 | mod bound;
8 | mod case;
9 | mod dummy;
10 | mod opg;
11 | mod parsing_context;
12 | mod symbol;
13 |
14 | use proc_macro::TokenStream;
15 | use quote::quote;
16 |
17 | use self::opg::*;
18 |
19 | #[proc_macro_derive(OpgModel, attributes(opg))]
20 | pub fn derive_opg_model(input: TokenStream) -> TokenStream {
21 | let input = syn::parse_macro_input!(input as syn::DeriveInput);
22 | impl_derive_opg_model(input)
23 | .unwrap_or_else(to_compile_errors)
24 | .into()
25 | }
26 |
27 | fn to_compile_errors(errors: Vec) -> proc_macro2::TokenStream {
28 | let compile_errors = errors.iter().map(syn::Error::to_compile_error);
29 | quote!(#(#compile_errors)*)
30 | }
31 |
--------------------------------------------------------------------------------
/opg_derive/src/dummy.rs:
--------------------------------------------------------------------------------
1 | pub fn wrap_in_const(
2 | trait_: &str,
3 | ty: &syn::Ident,
4 | code: proc_macro2::TokenStream,
5 | ) -> proc_macro2::TokenStream {
6 | let dummy_const = if cfg!(underscore_consts) {
7 | quote::format_ident!("_")
8 | } else {
9 | quote::format_ident!("_IMPL_{}_FOR_{}", trait_, unraw(ty))
10 | };
11 |
12 | let use_opg = quote::quote! {
13 | #[allow(rust_2018_idioms, clippy::useless_attribute)]
14 | extern crate opg as _opg;
15 | };
16 |
17 | quote::quote! {
18 | #[doc(hidden)]
19 | #[allow(non_upper_case_globals, unused_attributes, unused_qualifications)]
20 | const #dummy_const: () = {
21 | #use_opg
22 | #code
23 | };
24 | }
25 | }
26 |
27 | pub fn unraw(ident: &syn::Ident) -> String {
28 | ident.to_string().trim_start_matches("r#").to_owned()
29 | }
30 |
--------------------------------------------------------------------------------
/opg/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "opg"
3 | description = "Rust OpenAPI 3.0 docs generator"
4 | authors = ["Ivan Kalinin "]
5 | license = "Apache-2.0"
6 | version = "0.2.1"
7 | repository = "https://github.com/Rexagon/opg"
8 | keywords = ["openapi", "documentation", "generator"]
9 | categories = ["encoding"]
10 | include = ["src/**/*.rs", "README.md", "LICENSE"]
11 | edition = "2018"
12 |
13 | [dependencies]
14 | http = "0.2"
15 | opg_derive = { version = "=0.1.0", path = "../opg_derive" }
16 | serde = { version = "1", features = ["derive"] }
17 | either = "1.5"
18 | uuid = { version = "1", optional = true }
19 | chrono = { version = "0.4", optional = true }
20 |
21 | [dev-dependencies]
22 | opg_derive = { version = "0.1", path = "../opg_derive" }
23 |
24 | [features]
25 | default = ["const_generics"]
26 | const_generics = []
27 |
28 | [package.metadata.docs.rs]
29 | targets = ["x86_64-unknown-linux-gnu"]
30 |
--------------------------------------------------------------------------------
/opg_derive/src/parsing_context.rs:
--------------------------------------------------------------------------------
1 | use quote::ToTokens;
2 | use std::cell::RefCell;
3 |
4 | #[derive(Default)]
5 | pub struct ParsingContext {
6 | errors: RefCell
19 |
20 | #### Example:
21 | > Or see more [here](https://github.com/Rexagon/opg/tree/master/test_suite/tests)
22 |
23 | ```rust
24 | use opg::*;
25 | use serde::{Serialize, Deserialize};
26 |
27 | #[derive(Serialize, Deserialize, OpgModel)]
28 | #[serde(rename_all = "camelCase")]
29 | #[opg("Simple enum")]
30 | enum SimpleEnum {
31 | Test,
32 | Another,
33 | Yay,
34 | }
35 |
36 | #[derive(Serialize, Deserialize, OpgModel)]
37 | #[opg("newtype string", format = "id", example = "abcd0001")]
38 | struct NewType(String);
39 |
40 | #[derive(Serialize, Deserialize, OpgModel)]
41 | struct SimpleStruct {
42 | first_field: i32,
43 | #[opg("Field description")]
44 | second: String,
45 | }
46 |
47 | #[derive(Serialize, Deserialize, OpgModel)]
48 | #[serde(rename_all = "kebab-case")]
49 | enum ExternallyTaggedEnum {
50 | Test(String),
51 | AnotherTest(String, #[opg("Second")] String),
52 | }
53 |
54 | #[derive(Serialize, Deserialize, OpgModel)]
55 | #[serde(untagged)]
56 | enum UntaggedEnum {
57 | First {
58 | value: NewType,
59 | },
60 | #[opg("Variant description")]
61 | Second {
62 | #[opg("Inlined struct", inline)]
63 | another: SimpleStruct,
64 | },
65 | }
66 |
67 | #[derive(Serialize, Deserialize, OpgModel)]
68 | #[serde(tag = "tag", rename_all = "lowercase")]
69 | enum InternallyTaggedEnum {
70 | First(SimpleStruct),
71 | Second { field: String },
72 | }
73 |
74 | #[derive(Serialize, Deserialize, OpgModel)]
75 | #[serde(tag = "tag", content = "content", rename_all = "lowercase")]
76 | enum AdjacentlyTaggedEnum {
77 | First(String),
78 | Second(NewType, NewType),
79 | }
80 |
81 | #[derive(Serialize, Deserialize, OpgModel)]
82 | #[serde(rename_all = "camelCase")]
83 | struct TypeChangedStruct {
84 | #[serde(with = "chrono::naive::serde::ts_milliseconds")]
85 | #[opg("UTC timestamp in milliseconds", integer, format = "int64")]
86 | pub timestamp: chrono::NaiveDateTime,
87 | }
88 |
89 | #[derive(Serialize, Deserialize, OpgModel)]
90 | struct StructWithComplexObjects {
91 | #[serde(skip_serializing_if = "Option::is_none")]
92 | #[opg(optional)]
93 | super_optional: Option