Code Monkey home page Code Monkey logo

opg's Introduction

opg

Rust OpenAPI 3.0 docs generator

GitHub GitHub Workflow Status Crates.io Version Docs.rs

Example:

Or see more here

use opg::*;
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, OpgModel)]
#[serde(rename_all = "camelCase")]
#[opg("Simple enum")]
enum SimpleEnum {
    Test,
    Another,
    Yay,
}

#[derive(Serialize, Deserialize, OpgModel)]
#[opg("newtype string", format = "id", example = "abcd0001")]
struct NewType(String);

#[derive(Serialize, Deserialize, OpgModel)]
struct SimpleStruct {
    first_field: i32,
    #[opg("Field description")]
    second: String,
}

#[derive(Serialize, Deserialize, OpgModel)]
#[serde(rename_all = "kebab-case")]
enum ExternallyTaggedEnum {
    Test(String),
    AnotherTest(String, #[opg("Second")] String),
}

#[derive(Serialize, Deserialize, OpgModel)]
#[serde(untagged)]
enum UntaggedEnum {
    First {
        value: NewType,
    },
    #[opg("Variant description")]
    Second {
        #[opg("Inlined struct", inline)]
        another: SimpleStruct,
    },
}

#[derive(Serialize, Deserialize, OpgModel)]
#[serde(tag = "tag", rename_all = "lowercase")]
enum InternallyTaggedEnum {
    First(SimpleStruct),
    Second { field: String },
}

#[derive(Serialize, Deserialize, OpgModel)]
#[serde(tag = "tag", content = "content", rename_all = "lowercase")]
enum AdjacentlyTaggedEnum {
    First(String),
    Second(NewType, NewType),
}

#[derive(Serialize, Deserialize, OpgModel)]
#[serde(rename_all = "camelCase")]
struct TypeChangedStruct {
    #[serde(with = "chrono::naive::serde::ts_milliseconds")]
    #[opg("UTC timestamp in milliseconds", integer, format = "int64")]
    pub timestamp: chrono::NaiveDateTime,
}

#[derive(Serialize, Deserialize, OpgModel)]
struct StructWithComplexObjects {
    #[serde(skip_serializing_if = "Option::is_none")]
    #[opg(optional)]
    super_optional: Option<Option<String>>,
    field: Option<String>,
    boxed: Box<Option<i32>>,
}

#[derive(Serialize, OpgModel)]
struct GenericStructWithRef<'a, T> {
    message: &'a str,
    test: T,
}

#[derive(Serialize, OpgModel)]
struct SuperResponse {
    simple_enum: SimpleEnum,
    #[serde(rename = "new_type")]
    newtype: NewType,
    externally_tagged_enum: ExternallyTaggedEnum,
    untagged_enum: UntaggedEnum,
    internally_tagged_enum: InternallyTaggedEnum,
    adjacently_tagged_enum: AdjacentlyTaggedEnum,
    type_changed_struct: TypeChangedStruct,
    struct_with_complex_objects: StructWithComplexObjects,
}

#[test]
fn print_api() {
    let test = describe_api! {
        info: {
            title: "My super API",
            version: "0.0.0",
        },
        tags: {internal, admin("Super admin methods")},
        servers: {
            "https://my.super.server.com/v1",
        },
        security_schemes: {
            (http "bearerAuth"): {
                scheme: Bearer,
                bearer_format: "JWT",
            },
        },
        paths: {
            ("hello" / "world" / { paramTest: String }): {
                summary: "Some test group of requests",
                description: "Another test description",
                parameters: {
                    (header "x-request-id"): {
                        description: "Test",
                        required: true,
                    },
                },
                GET: {
                    tags: {internal},
                    summary: "Small summary",
                    description: "Small description",
                    deprecated: true,
                    parameters: {
                        (query someParam: u32): {
                            description: "Test",
                        }
                    },
                    200: String,
                    418 ("Optional response description"): String
                },
                POST: {
                    tags: {admin},
                    security: {"bearerAuth"},
                    body: {
                        description: "Some interesting description",
                        schema: GenericStructWithRef<'static, i64>,
                        required: true,
                    },
                    200: SuperResponse,
                    callbacks: {
                        callbackUrl: {
                            ("callbackUrl"): {
                                POST: {
                                    200: std::vec::Vec<String>,
                                }
                            }
                        }
                    }
                }
            }
        }
    };

    println!("{}", serde_yaml::to_string(&test).unwrap());
}
Result:

---
openapi: 3.0.3
info:
  title: My super API
  version: 0.0.0
tags:
  - name: admin
    description: Super admin methods
  - name: internal
servers:
  - url: "https://my.super.server.com/v1"
paths:
  "/hello/world/{paramTest}":
    summary: Some test group of requests
    description: Another test description
    get:
      tags:
        - internal
      summary: Small summary
      description: Small description
      deprecated: true
      responses:
        200:
          description: OK
          content:
            application/json:
              schema:
                type: string
        418:
          description: Optional response description
          content:
            application/json:
              schema:
                type: string
      parameters:
        - name: someParam
          description: Test
          in: query
          schema:
            type: integer
            format: uint32
    post:
      tags:
        - admin
      security:
        - bearerAuth: []
      requestBody:
        required: true
        description: Some interesting description
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GenericStructWithRef"
      responses:
        200:
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuperResponse"
      callbacks:
        callbackUrl:
          /callbackUrl:
            post:
              responses:
                200:
                  description: OK
                  content:
                    application/json:
                      schema:
                        type: array
                        items:
                          type: string
    parameters:
      - name: paramTest
        in: path
        required: true
        schema:
          type: string
      - name: x-request-id
        description: Test
        in: header
        required: true
        schema:
          type: string
components:
  schemas:
    AdjacentlyTaggedEnum:
      type: object
      properties:
        content:
          oneOf:
            - type: string
            - type: array
              items:
                oneOf:
                  - $ref: "#/components/schemas/NewType"
                  - $ref: "#/components/schemas/NewType"
        tag:
          description: AdjacentlyTaggedEnum type variant
          type: string
          enum:
            - first
            - second
          example: first
      required:
        - tag
        - content
    ExternallyTaggedEnum:
      type: object
      additionalProperties:
        oneOf:
          - type: string
          - type: array
            items:
              oneOf:
                - type: string
                - description: Second
                  type: string
    GenericStructWithRef:
      type: object
      properties:
        message:
          type: string
        test:
          type: integer
          format: int64
      required:
        - message
        - test
    InternallyTaggedEnum:
      oneOf:
        - type: object
          properties:
            first_field:
              type: integer
              format: int32
            second:
              description: Field description
              type: string
            tag:
              description: InternallyTaggedEnum type variant
              type: string
              enum:
                - first
              example: first
          required:
            - first_field
            - second
            - tag
        - type: object
          properties:
            field:
              type: string
            tag:
              description: InternallyTaggedEnum type variant
              type: string
              enum:
                - second
              example: second
          required:
            - field
            - tag
    NewType:
      description: newtype string
      type: string
      format: id
      example: abcd0001
    SimpleEnum:
      description: Simple enum
      type: string
      enum:
        - test
        - another
        - yay
      example: test
    StructWithComplexObjects:
      type: object
      properties:
        boxed:
          nullable: true
          type: integer
          format: int32
        field:
          nullable: true
          type: string
        super_optional:
          nullable: true
          type: string
      required:
        - field
        - boxed
    SuperResponse:
      type: object
      properties:
        adjacently_tagged_enum:
          $ref: "#/components/schemas/AdjacentlyTaggedEnum"
        externally_tagged_enum:
          $ref: "#/components/schemas/ExternallyTaggedEnum"
        internally_tagged_enum:
          $ref: "#/components/schemas/InternallyTaggedEnum"
        new_type:
          $ref: "#/components/schemas/NewType"
        simple_enum:
          $ref: "#/components/schemas/SimpleEnum"
        struct_with_complex_objects:
          $ref: "#/components/schemas/StructWithComplexObjects"
        type_changed_struct:
          $ref: "#/components/schemas/TypeChangedStruct"
        untagged_enum:
          $ref: "#/components/schemas/UntaggedEnum"
      required:
        - simple_enum
        - new_type
        - externally_tagged_enum
        - untagged_enum
        - internally_tagged_enum
        - adjacently_tagged_enum
        - type_changed_struct
        - struct_with_complex_objects
    TypeChangedStruct:
      type: object
      properties:
        timestamp:
          description: UTC timestamp in milliseconds
          type: integer
          format: int64
      required:
        - timestamp
    UntaggedEnum:
      oneOf:
        - type: object
          properties:
            value:
              $ref: "#/components/schemas/NewType"
          required:
            - value
        - description: Variant description
          type: object
          properties:
            another:
              description: Inlined struct
              type: object
              properties:
                first_field:
                  type: integer
                  format: int32
                second:
                  description: Field description
                  type: string
              required:
                - first_field
                - second
          required:
            - another
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

opg's People

Contributors

rexagon avatar matteojoliveau avatar pashinov avatar m1guelpf avatar 0xdeafbeef avatar

Stargazers

 avatar Timo Wildhage avatar Etch avatar 听风 avatar Eugene Nosenko avatar Stanislav avatar Abdulrahman Salah avatar  avatar  avatar Rom's avatar Oyebanji Jacob Mayowa avatar orange soeur avatar Justin Bennett avatar Pawel Chojnacki avatar Mathis EON avatar Christoph Grabo avatar Russ avatar Willi Kappler avatar  avatar Mohammed Makhlouf (Mak) avatar Jan Vandepitte avatar Ankur Srivastava avatar Vlad Frolov avatar Diana avatar Mario Fleischhacker avatar Stefan Vatov avatar  avatar zan avatar Maxim Belousov avatar Andrey Ermilov avatar Artem avatar Dale Jefferson (RNF) avatar speedsx avatar Stanislav Tkach avatar

Watchers

James Cloos avatar  avatar Mathis EON avatar

opg's Issues

Add proc-macro for paths

Instead of describing the paths in the describe_api macro, I would like to have some proc-macro.

Instead of this:

describe_api! {
    // ...
    paths: {
        ("some" / "path" / { param: String }): {
            GET: {
                description: "Get smth",
                200: Smth,
                500: ApiError
            },
            POST: {
                description: "Do smth",
                200: None,
                500: ApiError
            },
        }
    }
}

describe api like this:

#[opg::path(
    path = "some/path/{param}",
    method = "GET",
    description = "Get smth",
    on_200 = "Smth",
    on_500 = "ApiError",
)]
async fn get_smth() -> Result<Smth, ApiError> {...}

#[opg::path(
    path = "some/path/{param}",
    method = "POST",
    description = "Get smth",
    on_200 = "None",
    on_500 = "ApiError",
)]
async fn do_smth() -> Result<(), ApiError> {...}

// ...

describe_api! {
    // ...
    paths: {
        get_smth,
        do_smth,
    }
}

Add support for repr structs

Currently simple C-like enums are serialized as string enums:

#[derive(Serialize)]
enum Test {
    First = 1,
    Second = 2,
}
type: string
enum:
  - First
  - Second
example: First

It should be something like this:

oneOf:
  - type: integer
    description: First variant
    example: 1
  - type: integer
    description: Second variant
    example: 2

Add support for generic structs/enums/etc.

opg does not support generic structs.

#[derive(Debug, serde::Serialize, opg::OpgModel)]
struct TestStruct<T: std::fmt::Debug> {
    inner: T,
}

#[derive(Debug, serde::Serialize, opg::OpgModel)]
struct TestSubStruct1 {
    a: i32,
    b: i64,
}

#[derive(Debug, serde::Serialize, opg::OpgModel)]
struct TestSubStruct2<'a> {
    c: &'a i32,
    d: String,
}

describe_api!(
    info: {
        title: "title",
        description: "description",
        version: "0.1.0",
    },
    paths: {
        ("some" / "path" / "one"): {
            GET: {
                description: "Get smth 1",
                200: TestStruct<TestSubStruct1>,
            },
        },
        ("some" / "path" / "two"): {
            GET: {
                description: "Get smth 1",
                200: TestStruct<TestSubStruct2<'static>>,
            },
        },
    }
)

Converts to this:

---
openapi: 3.0.3
info:
  title: title
  description: description
  version: 0.1.0
paths:
  /some/path/one:
    get:
      description: Get smth 1
      responses:
        200:
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TestStruct"
  /some/path/two:
    get:
      description: Get smth 1
      responses:
        200:
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TestStruct"
components:
  schemas:
    TestStruct:
      type: object
      properties:
        inner:
          $ref: "#/components/schemas/TestSubStruct1"
      required:
        - inner
    TestSubStruct1:
      type: object
      properties:
        a:
          type: integer
          format: int32
        b:
          type: integer
          format: int64
      required:
        - a
        - b

There is the only first TestStruct<TestSubStruct1> type.

Externally tagged enums don't work correctly when using struct variants

When generating schemas for externally tagged enums containing struct variants, the oneOf block gets embedded inside additionalProperties instead of being top-level in the schema.

I tried to debug the proc macro, but my knowledge of Rust macros is fairly lacking and I wasn't able to track down the point where the schema gets generated. If you could point me in the right direction I could open a PR to fix this.

Example

Rust

#[derive(Clone, Debug, Deserialize, Serialize, OpgModel)]
#[serde(rename_all = "camelCase")]
pub enum ExampleEnum {
    First { hello: String },
    Second { world: String },
}

Example payload

{
  "first": {
    "hello": "hi!",
  }
}

{
  "second": {
    "world": "mondo",
  }
}

Currently generated schema

"ExampleEnum": {
  "type": "object",
  "additionalProperties": {
    "oneOf": [
      {
        "type": "object",
        "properties": {
          "hello": {
            "type": "string"
          }
        },
        "required": [
          "hello"
        ]
      },
      {
        "type": "object",
        "properties": {
          "world": {
            "type": "string"
          }
        },
        "required": [
          "world"
        ]
      }
    ]
  }
},

Expected Schema

"ExampleEnum": {
  "oneOf": [
    {
      "type": "object",
      "properties": {
        "first": {
          "type": "object",
          "properties": {
            "hello": {
              "type": "string"
            }
          },
          "required": [
            "hello"
          ]
        }
      },
      "required": [
        "first"
      ]
    },
    {
      "type": "object",
      "properties": {
        "second": {
          "type": "object",
          "properties": {
            "world": {
              "type": "string"
            }
          },
          "required": [
            "world"
          ]
        }
      },
      "required": [
        "second"
      ]
    }
  ]
}

Add allowed values (enum) on field

I think it will look like:

#[derive(opg::OpgModel)]
struct Struct {
    #[opg("Some value", enum = "Value1,Value2,Value3")]
    value: String,
}

Enum without associated value, causes derive macro to hit "entered unreachable code" error

steps to reproduce:

Attempt to use enum like this one:

#[derive(Serialize, Deserialize, OpgModel)]
#[serde(tag = "req_type", content = "content")]
pub enum Repro {
    TypeA(),
    TybeB
}

Error:

error: proc-macro derive panicked
  --> src/data.rs:64:34
   |
64 | #[derive(Serialize, Deserialize, OpgModel)]
   |                                  ^^^^^^^^
   |
   = help: message: internal error: entered unreachable code

error: aborting due to previous error

error: could not compile `...`

Just encountered this error, using this awesome! crate. Haven't yet had time to dig deeper.

Add support for recursive types

Something like this now causes recursion:

#[derive(Serialize, OpgModel)]
struct Node {
  value: i32,
  next: Option<Box<Node>>,
}

Add docs

  • Document all models
  • Describe api macro
  • Add more usage examples

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.