Slide 1

Slide 1 text

OpenAPI e Elixir* (*e qualquer outra linguagem) 1

Slide 2

Slide 2 text

@lucasmazza Ruby, TS, HCL/YML Platform @ Tremendous https://lucasmazza.website/ 2

Slide 3

Slide 3 text

3

Slide 4

Slide 4 text

4

Slide 5

Slide 5 text

5

Slide 6

Slide 6 text

6

Slide 7

Slide 7 text

OpenAPI Specification 7

Slide 8

Slide 8 text

The OpenAPI Specification (OAS) defines a standard, language-agnostic interface to HTTP APIs which allows both humans and computers to discover and understand the capabilities of the service [...] — https://www.openapis.org 8

Slide 9

Slide 9 text

• 2011 - Swagger 1.0 • 2015 - OpenAPI 2.0 • 2017 - OpenAPI 3.0 • 2021 - OpenAPI 3.1 9

Slide 10

Slide 10 text

⚠ YAML 10

Slide 11

Slide 11 text

# cat doc/openapi.yml openapi: 3.1.0 info: title: Tremendous API # ... servers: # ... security: # ... paths: # ... components: # ... 11

Slide 12

Slide 12 text

# cat doc/openapi.yml openapi: 3.1.0 info: title: Tremendous API # ... servers: - url: https://testflight.tremendous.com/api/v2 description: Sandbox environment - url: https://www.tremendous.com/api/v2 description: Production environment security: # ... paths: # ... components: # ... 12

Slide 13

Slide 13 text

# cat doc/openapi.yml openapi: 3.1.0 info: # ... servers: # ... security: # ... paths: "/products": get: # ... post: # ... "/products/{id}": get: # ... components: # ... 13

Slide 14

Slide 14 text

# cat doc/openapi.yml openapi: 3.1.0 info: # ... servers: # ... security: # ... paths: # ... components: schemas: # ... responses: # ... parameters: # ... examples: # ... requestBodies: # ... 14

Slide 15

Slide 15 text

Operations & Schemas 15

Slide 16

Slide 16 text

Operations 16

Slide 17

Slide 17 text

operation: request + response(s) • path + method • parameters • requestBody • responses • ... 17

Slide 18

Slide 18 text

curl -X POST \ -H "Accept: application/json" \ -d "@body.json" \ https://myapp.dev/posts 18

Slide 19

Slide 19 text

paths: "/posts": post: summary: Creates a new post on the blog operationId: createPost requestBody: content: application/json: schema: $ref: "#/components/schemas/PostParams" responses: "200": content: application/json: schema: $ref: "#/components/schemas/Post" "422": content: application/json: schema: $ref: "#/components/schemas/ValidationErrors" 19

Slide 20

Slide 20 text

paths: "/posts": post: summary: Creates a new post on the blog operationId: createPost requestBody: content: application/json: schema: $ref: "#/components/schemas/PostParams" responses: "200": content: application/json: schema: $ref: "#/components/schemas/Post" "422": content: application/json: schema: $ref: "#/components/schemas/ValidationErrors" 19

Slide 21

Slide 21 text

paths: "/posts": post: summary: Creates a new post on the blog operationId: createPost requestBody: content: application/json: schema: $ref: "#/components/schemas/PostParams" responses: "200": content: application/json: schema: $ref: "#/components/schemas/Post" "422": content: application/json: schema: $ref: "#/components/schemas/ValidationErrors" 19

Slide 22

Slide 22 text

paths: "/posts": post: summary: Creates a new post on the blog operationId: createPost requestBody: content: application/json: schema: $ref: "#/components/schemas/PostParams" responses: "200": content: application/json: schema: $ref: "#/components/schemas/Post" "422": content: application/json: schema: $ref: "#/components/schemas/ValidationErrors" 19

Slide 23

Slide 23 text

paths: "/posts": post: summary: Creates a new post on the blog operationId: createPost requestBody: content: application/json: schema: $ref: "#/components/schemas/PostParams" responses: "200": content: application/json: schema: $ref: "#/components/schemas/Post" "422": content: application/json: schema: $ref: "#/components/schemas/ValidationErrors" 19

Slide 24

Slide 24 text

curl -H "Accept: application/json" \ https://myapp.dev/products?limit=50 20

Slide 25

Slide 25 text

paths: "/products": get: summary: Retrieve products operationId: listProducts parameters: - name: limit in: query required: false schema: type: integer default: 25 responses: "200": content: application/json: schema: $ref: "#/components/schemas/ProductsResponse" 21

Slide 26

Slide 26 text

paths: "/products": get: summary: Retrieve products operationId: listProducts parameters: - name: limit in: query required: false schema: type: integer default: 25 responses: "200": content: application/json: schema: $ref: "#/components/schemas/ProductsResponse" 21

Slide 27

Slide 27 text

paths: "/products": get: summary: Retrieve products operationId: listProducts parameters: - name: limit in: query required: false schema: type: integer default: 25 responses: "200": content: application/json: schema: $ref: "#/components/schemas/ProductsResponse" 21

Slide 28

Slide 28 text

paths: "/rewards": $ref: "./resources/rewards.yml#/rewards" "/rewards/{id}": $ref: "./resources/rewards.yml#/reward" "/orders": $ref: "./resources/orders.yml#/orders" "/orders/{id}": $ref: "./resources/orders.yml#/order" 22

Slide 29

Slide 29 text

Schemas 23

Slide 30

Slide 30 text

curl -H "Accept: application/json" \ https://myapp.dev/me # { # "id": "9e32ce59-8de9-42f6-b631-40381bd9aeb5", # "email": "[email protected]", # "last_signed_at": "2024-07-31T18:30:49.027203Z" # } 24

Slide 31

Slide 31 text

curl -H "Accept: application/json" \ https://myapp.dev/me # { # "id": "9e32ce59-8de9-42f6-b631-40381bd9aeb5", # "email": "[email protected]", # "last_signed_at": "2024-07-31T18:30:49.027203Z" # } 24

Slide 32

Slide 32 text

curl -H "Accept: application/json" \ https://myapp.dev/me # { # "id": "9e32ce59-8de9-42f6-b631-40381bd9aeb5", # "email": "[email protected]", # "last_signed_at": "2024-07-31T18:30:49.027203Z" # } 24

Slide 33

Slide 33 text

components: schemas: CurrentUser: type: object properties: id: type: string format: uuid email: type: string format: email name: type: string last_signed_at: type: string format: date-time required: - id - email - last_signed_at 25

Slide 34

Slide 34 text

components: schemas: CurrentUser: type: object properties: id: type: string format: uuid email: type: string format: email name: type: string last_signed_at: type: string format: date-time required: - id - email - last_signed_at 25

Slide 35

Slide 35 text

components: schemas: CurrentUser: type: object properties: id: type: string format: uuid email: type: string format: email name: type: string last_signed_at: type: string format: date-time required: - id - email - last_signed_at 25

Slide 36

Slide 36 text

paths: "/me": get: summary: Retrieves the current user operationId: getCurrentUser responses: "200": content: application/json: schema: $ref: "#/components/schemas/CurrentUser" 26

Slide 37

Slide 37 text

components: schemas: TremendousId: type: string pattern: "[A-Z0-9]{12}" example: NQM23TPL3GH1 Product: type: object properties: id: $ref: "#/components/schemas/TremendousId" required: - id 27

Slide 38

Slide 38 text

components: schemas: TremendousId: type: string pattern: "[A-Z0-9]{12}" example: NQM23TPL3GH1 Product: type: object properties: id: $ref: "#/components/schemas/TremendousId" required: - id 27

Slide 39

Slide 39 text

paths: "/rewards/{id}": get: summary: Retrieves a single reward operationId: get-reward parameters: - name: id in: path required: true schema: $ref: "#/components/schemas/TremendousId" 28

Slide 40

Slide 40 text

29

Slide 41

Slide 41 text

Composição / Herança 30

Slide 42

Slide 42 text

anyOf / allOf / oneOf components: schemas: BaseError: type: object # ... ValidationError: allOf: # `ValidationError` extende `BaseError` e include mais propriedades - $ref: "#/components/schemas/BaseError" - type: object properties: fields: type: array items: type: string 31

Slide 43

Slide 43 text

anyOf / allOf / oneOf # `GET /users/{id}` response com um `User` ou um `BannedUser` # de acordo com o valor de `banned` paths: "/users/{id}": get: # ... responses: "200": content: application/json: schema: oneOf: - $ref: "#/components/schemas/User" - $ref: "#/components/schemas/BannedUser" # Opcional: discriminator: property_name: banned mapping: true: "#/components/schemas/BannedUser" false: "#/components/schemas/User" 32

Slide 44

Slide 44 text

33

Slide 45

Slide 45 text

elixir / phoenix? 34

Slide 46

Slide 46 text

.yml ! .ex 35

Slide 47

Slide 47 text

open_api_spex Leverage Open API Specification 3 (formerly Swagger) to document, test, validate and explore your Plug and Phoenix APIs. def deps do [ {:open_api_spex, "~> 3.18"} ] end 36

Slide 48

Slide 48 text

defmodule MyAppWeb.ApiSpec do alias OpenApiSpex.{Info, OpenApi, Paths} alias MyAppWeb.Router @behaviour OpenApi @impl OpenApi def spec do %OpenApi{ servers: [], info: %Info{title: "My API", version: "1.0.0"}, paths: Paths.from_router(Router) } |> OpenApiSpex.resolve_schema_modules() end end 37

Slide 49

Slide 49 text

defmodule MyAppWeb.ApiSpec do alias OpenApiSpex.{Info, OpenApi, Paths} alias MyAppWeb.Router @behaviour OpenApi @impl OpenApi def spec do %OpenApi{ servers: [], info: %Info{title: "My API", version: "1.0.0"}, paths: Paths.from_router(Router) } |> OpenApiSpex.resolve_schema_modules() end end 37

Slide 50

Slide 50 text

defmodule MyAppWeb.ApiSpec do alias OpenApiSpex.{Info, OpenApi, Paths} alias MyAppWeb.Router @behaviour OpenApi @impl OpenApi def spec do %OpenApi{ servers: [], info: %Info{title: "My API", version: "1.0.0"}, paths: Paths.from_router(Router) } |> OpenApiSpex.resolve_schema_modules() end end 37

Slide 51

Slide 51 text

defmodule MyAppWeb.ApiSpec do alias OpenApiSpex.{Info, OpenApi, Paths} alias MyAppWeb.Router @behaviour OpenApi @impl OpenApi def spec do %OpenApi{ servers: [], info: %Info{title: "My API", version: "1.0.0"}, paths: Paths.from_router(Router) } |> OpenApiSpex.resolve_schema_modules() end end 37

Slide 52

Slide 52 text

# lib/my_app_web/router.ex pipeline :api do plug :accepts, ["json"] plug OpenApiSpex.Plug.PutApiSpec, module: MyAppWeb.ApiSpec end scope "/api" do pipe_through :api resources "/users", MyAppWeb.UserController, only: [:create, :index, :show] get "/openapi", OpenApiSpex.Plug.RenderSpec, [] end # mix openapi.spec.json --spec MyAppWeb.ApiSpec --filename doc/openapi.json # mix openapi.spec.yaml --spec MyAppWeb.ApiSpec --filename doc/openapi.yaml 38

Slide 53

Slide 53 text

# lib/my_app_web/router.ex pipeline :api do plug :accepts, ["json"] plug OpenApiSpex.Plug.PutApiSpec, module: MyAppWeb.ApiSpec end scope "/api" do pipe_through :api resources "/users", MyAppWeb.UserController, only: [:create, :index, :show] get "/openapi", OpenApiSpex.Plug.RenderSpec, [] end # mix openapi.spec.json --spec MyAppWeb.ApiSpec --filename doc/openapi.json # mix openapi.spec.yaml --spec MyAppWeb.ApiSpec --filename doc/openapi.yaml 38

Slide 54

Slide 54 text

# lib/my_app_web/router.ex pipeline :api do plug :accepts, ["json"] plug OpenApiSpex.Plug.PutApiSpec, module: MyAppWeb.ApiSpec end scope "/api" do pipe_through :api resources "/users", MyAppWeb.UserController, only: [:create, :index, :show] get "/openapi", OpenApiSpex.Plug.RenderSpec, [] end # mix openapi.spec.json --spec MyAppWeb.ApiSpec --filename doc/openapi.json # mix openapi.spec.yaml --spec MyAppWeb.ApiSpec --filename doc/openapi.yaml 38

Slide 55

Slide 55 text

# lib/my_app_web/router.ex pipeline :api do plug :accepts, ["json"] plug OpenApiSpex.Plug.PutApiSpec, module: MyAppWeb.ApiSpec end scope "/api" do pipe_through :api resources "/users", MyAppWeb.UserController, only: [:create, :index, :show] get "/openapi", OpenApiSpex.Plug.RenderSpec, [] end # mix openapi.spec.json --spec MyAppWeb.ApiSpec --filename doc/openapi.json # mix openapi.spec.yaml --spec MyAppWeb.ApiSpec --filename doc/openapi.yaml 38

Slide 56

Slide 56 text

Operations 39

Slide 57

Slide 57 text

defmodule MyAppWeb.UserController do use MyAppWeb, :controller use OpenApiSpex.ControllerSpecs alias MyAppWeb.Schemas.{UserParams, UserResponse} operation :update, summary: "Update user", parameters: [ id: [in: :path, description: "User ID", type: :integer, example: 1001] ], request_body: {"User params", "application/json", UserParams}, responses: [ ok: {"User response", "application/json", UserResponse} ] def update(conn, %{"id" => id}) do # ... end end 40

Slide 58

Slide 58 text

defmodule MyAppWeb.UserController do use MyAppWeb, :controller use OpenApiSpex.ControllerSpecs alias MyAppWeb.Schemas.{UserParams, UserResponse} operation :update, summary: "Update user", parameters: [ id: [in: :path, description: "User ID", type: :integer, example: 1001] ], request_body: {"User params", "application/json", UserParams}, responses: [ ok: {"User response", "application/json", UserResponse} ] def update(conn, %{"id" => id}) do # ... end end 40

Slide 59

Slide 59 text

defmodule MyAppWeb.UserController do use MyAppWeb, :controller use OpenApiSpex.ControllerSpecs alias MyAppWeb.Schemas.{UserParams, UserResponse} operation :update, summary: "Update user", parameters: [ id: [in: :path, description: "User ID", type: :integer, example: 1001] ], request_body: {"User params", "application/json", UserParams}, responses: [ ok: {"User response", "application/json", UserResponse} ] def update(conn, %{"id" => id}) do # ... end end 40

Slide 60

Slide 60 text

defmodule MyAppWeb.UserController do use MyAppWeb, :controller def open_api_operation(:update) do %OpenApiSpex.Operation{ # ... } end end 41

Slide 61

Slide 61 text

mix openapi.spec.yaml --spec MyAppWeb.ApiSpec 42

Slide 62

Slide 62 text

paths: /api/users/{id}: put: operationId: MyAppWeb.UserController.update parameters: - description: User ID example: 1001 in: path name: id required: true schema: type: integer requestBody: content: application/json: schema: $ref: "#/components/schemas/UserParams" description: User params required: false responses: 200: content: application/json: schema: $ref: "#/components/schemas/UserResponse" description: User response summary: Update user patch: # ... 43

Slide 63

Slide 63 text

Schemas 44

Slide 64

Slide 64 text

defmodule MyAppWeb.Schemas.User do alias OpenApiSpex.Schema require OpenApiSpex OpenApiSpex.schema(%{ type: :object, properties: %{ id: %Schema{type: :integer, description: "User ID"}, name: %Schema{type: :string, description: "User name", pattern: ~r/[a-zA-Z][a-zA-Z0-9_]+/}, email: %Schema{type: :string, description: "Email address", format: :email}, birthday: %Schema{type: :string, description: "Birth date", format: :date}, inserted_at: %Schema{ type: :string, description: "Creation timestamp", format: :"date-time" }, updated_at: %Schema{type: :string, description: "Update timestamp", format: :"date-time"} }, required: [:name, :email], }) end 45

Slide 65

Slide 65 text

defmodule MyAppWeb.Schemas.User do alias OpenApiSpex.Schema @behaviour OpenApiSpex.Schema def schema do %Schema{ title: "User", type: :object, properties: %{ id: %Schema{type: :integer, description: "User ID"}, name: %Schema{type: :string, description: "User name", pattern: ~r/[a-zA-Z][a-zA-Z0-9_]+/}, email: %Schema{type: :string, description: "Email address", format: :email}, birthday: %Schema{type: :string, description: "Birth date", format: :date}, inserted_at: %Schema{ type: :string, description: "Creation timestamp", format: :"date-time" }, updated_at: %Schema{type: :string, description: "Update timestamp", format: :"date-time"} }, required: [:name, :email], } end end 46

Slide 66

Slide 66 text

! 47

Slide 67

Slide 67 text

! 48

Slide 68

Slide 68 text

Testes fixtures & assertions 49

Slide 69

Slide 69 text

fixtures & assertions defmodule MyAppWeb.Schemas.UserParams do alias OpenApiSpex.Schema @behaviour OpenApiSpex.Schema def schema do %Schema{ title: "UserParams", type: :object, properties: %{ # ... }, required: [:user], example: %{ user: %{ name: "John Wick", email: "[email protected]", birthday: "1964-07-12" } } } end end 50

Slide 70

Slide 70 text

fixtures & assertions defmodule MyAppWeb.Schemas.UserParams do alias OpenApiSpex.Schema @behaviour OpenApiSpex.Schema def schema do %Schema{ title: "UserParams", type: :object, properties: %{ # ... }, required: [:user], example: %{ user: %{ name: "John Wick", email: "[email protected]", birthday: "1964-07-12" } } } end end 50

Slide 71

Slide 71 text

fixtures & assertions import OpenApiSpex.TestAssertions test "renders user when data is valid", %{conn: conn, user: %User{id: id} = user} do api_spec = MyAppWeb.ApiSpec.spec() params = OpenApiSpex.Schema.example(MyAppWeb.Schemas.UserParams.schema()) assert_schema(params, "UserParams", api_spec) conn |> put(~p"/api/users/#{id}", params) |> json_response(200) |> assert_schema("UserResponse", api_spec) end 51

Slide 72

Slide 72 text

fixtures & assertions import OpenApiSpex.TestAssertions test "renders user when data is valid", %{conn: conn, user: %User{id: id} = user} do api_spec = MyAppWeb.ApiSpec.spec() params = OpenApiSpex.Schema.example(MyAppWeb.Schemas.UserParams.schema()) assert_schema(params, "UserParams", api_spec) conn |> put(~p"/api/users/#{id}", params) |> json_response(200) |> assert_schema("UserResponse", api_spec) end 51

Slide 73

Slide 73 text

fixtures & assertions 1) test update user renders user when data is valid (MyAppWeb.UserControllerTest) test/my_app_web/controllers/user_controller_test.exs:53 Value does not conform to schema UserResponse: Missing field: name at /user/name Missing field: email at /user/email %{"user" => %{"birthday" => "1964-07-12", "id" => 15, "inserted_at" => "2024-07-27T23:14:09Z", "updated_at" => "2024-07-27T23:14:09Z"}} code: |> assert_schema("UserResponse", api_spec) stacktrace: test/my_app_web/controllers/user_controller_test.exs:62: (test) 52

Slide 74

Slide 74 text

fixtures & assertions ! " # ! assert company = json["data"]["company"] assert user = json["data"]["user"] assert user["name"] assert user["email"] assert user["profile_picture"] assert company["vat"] assert company["billing_email"] assert company["name"] assert company["slug"] assert Map.has_key?(company, "msa_effective_date") assert Map.has_key?(company, "registration_number") ! " # ! 53

Slide 75

Slide 75 text

fixtures & assertions components: schemas: UserParams: example: user: birthday: 1964-07-12 email: [email protected] name: John Wick properties: # ... title: UserParams type: object 54

Slide 76

Slide 76 text

fixtures & assertions components: schemas: UserParams: example: user: birthday: 1964-07-12 email: [email protected] name: John Wick properties: # ... title: UserParams type: object 54

Slide 77

Slide 77 text

fixtures & assertions defmodule MyAppWeb.Schemas.ExamplesTest do use ExUnit.Case, async: true import OpenApiSpex.TestAssertions test "all examples are valid" do api_spec = MyAppWeb.ApiSpec.spec() for {name, schema} <- api_spec.components.schemas do example = OpenApiSpex.Schema.example(schema) assert_schema(example, name, api_spec) end end end 55

Slide 78

Slide 78 text

Geração de schemas 56

Slide 79

Slide 79 text

enums defmodule MyApp.Users.User do use Ecto.Schema import Ecto.Changeset schema "users" do # ... field :status, Ecto.Enum, values: [:invited, :active, :deleted] end end 57

Slide 80

Slide 80 text

enums defmodule MyApp.Users.User do use Ecto.Schema import Ecto.Changeset schema "users" do # ... field :status, Ecto.Enum, values: [:invited, :active, :deleted] end end 57

Slide 81

Slide 81 text

enums defmodule MyAppWeb.Schemas.Enum do alias OpenApiSpex.Schema def to_schema(module, field, opts \\ []) do values = Ecto.Enum.values(module, field) example = Keyword.get(opts, :example, Enum.fetch!(values, 0)) suffix = module |> Module.split() |> List.last() title = Keyword.get(opts, :title, "#{suffix}#{String.capitalize(to_string(field))}") %Schema{ title: title, type: type, enum: values, example: example, } end end 58

Slide 82

Slide 82 text

enums defmodule MyAppWeb.Schemas.UserStatus do @behaviour OpenApiSpex.Schema def schema do MyAppWeb.Schemas.Enum.to_schema(MyApp.Users.User, :status) end end 59

Slide 83

Slide 83 text

enums defmodule MyAppWeb.Schemas.User do alias OpenApiSpex.Schema @behaviour OpenApiSpex.Schema def schema do %Schema{ title: "User", type: :object, properties: %{ # ... status: MyAppWeb.Schemas.UserStatus, } } end end 60

Slide 84

Slide 84 text

enums components: schemas: User: properties: # ... status: $ref: '#/components/schemas/UserStatus' # ... UserStatus: enum: - invited - active - deleted example: invited title: User status type: string 61

Slide 85

Slide 85 text

plugs # Expõe o Swagger UI junto da sua spec OpenApiSpex.Plug.SwaggerUI # Atualiza `conn.params` e `conn.body_params` de acordo com a spec OpenApiSpex.Plug.Cast # Responde com `422` caso a requisição seja inválida OpenApiSpex.Plug.Validate OpenApiSpex.Plug.CastAndValidate 62

Slide 86

Slide 86 text

open_api_spex 63

Slide 87

Slide 87 text

open_api_spex • ✅ projeto estável e ativo 63

Slide 88

Slide 88 text

open_api_spex • ✅ projeto estável e ativo • ✅ sem dependências 63

Slide 89

Slide 89 text

open_api_spex • ✅ projeto estável e ativo • ✅ sem dependências • ✅ schema em Elixir é muito extensível 63

Slide 90

Slide 90 text

open_api_spex • ✅ projeto estável e ativo • ✅ sem dependências • ✅ schema em Elixir é muito extensível • " macros 63

Slide 91

Slide 91 text

open_api_spex • ✅ projeto estável e ativo • ✅ sem dependências • ✅ schema em Elixir é muito extensível • " macros • " pode gerar spec inválida 63

Slide 92

Slide 92 text

open_api_spex • ✅ projeto estável e ativo • ✅ sem dependências • ✅ schema em Elixir é muito extensível • " macros • " pode gerar spec inválida • " Não tem suporte completo a spec 3.1 63

Slide 93

Slide 93 text

E fora do Elixir? 64

Slide 94

Slide 94 text

linters 65

Slide 95

Slide 95 text

# https://github.com/redocly/redocly-cli npx @redocly/cli lint openapi.yml # https://github.com/stoplightio/spectral npx @stoplight/spectral-cli lint openapi.yml 66

Slide 96

Slide 96 text

documentação 67

Slide 97

Slide 97 text

68

Slide 98

Slide 98 text

69

Slide 99

Slide 99 text

70

Slide 100

Slide 100 text

documentação • https://swagger.io • https://readme.com • https://redocly.com • https://slatedocs.github.io/slate • ... 71

Slide 101

Slide 101 text

mocks 72

Slide 102

Slide 102 text

mocks https://github.com/zoubingwu/msw-auto-mock cd my-react-app npm add msw-auto-mock @faker-js/faker --save-dev npx msw-auto-mock ../backend-app/openapi.yml -o ./tests/mocks 73

Slide 103

Slide 103 text

mocks https://github.com/stoplightio/prism npx @stoplight/prism-cli mock ../backend-app/openapi.yml # Prism is listening on http://127.0.0.1:4010 npx @stoplight/prism-cli proxy ../backend-app/openapi.yml https://myapp.dev/ 74

Slide 104

Slide 104 text

wrappers / sdks 75

Slide 105

Slide 105 text

wrappers / sdks • https://buildwithfern.com/ - Saas / OSS • https://www.speakeasy.com/ - Saas • https://openapi-generator.tech/ - OSS 76

Slide 106

Slide 106 text

openapi-generator brew install openapi-generator # openjdk, lol # https://remote.com/resources/api/reference openapi-generator generate \ --input-spec remote.json \ --generator-name ruby \ --additional-properties gemName=remote \ --output ./remote-ruby-sdk 77

Slide 107

Slide 107 text

openapi-generator # docker docker run --rm \ -v ./remote-ruby-sdk:/remote-ruby-sdk \ -v ./remote.json:/remote.json \ openapitools/openapi-generator-cli generate \ --input-spec /remote.json \ --generator-name ruby \ --additional-properties gemName=remote \ --output /remote-ruby-sdk 78

Slide 108

Slide 108 text

openapi-generator # cd ./remote-ruby-sdk # bundle install # irb -Ilib require "remote" Remote.configure do |config| config.access_token = ENV["ACCESS_TOKEN"] end countries = Remote::CountriesApi.new response = countries.get_index_holiday("", "BRA", "2024") response.data.last(5).each do |holiday| puts "* #{holiday.observed_day} - #{holiday.name}" end # * 2024-10-12 - Our Lady of Aparecida (Nossa Senhora Aparecida) # * 2024-11-02 - Day of the Dead (Dia de Finados) # * 2024-11-15 - Proclamation of the Republic (Proclamação da República) # * 2024-11-20 - Black Awareness Day (Dia da Consciência Negra) # * 2024-12-25 - Christmas Day (Natal) 79

Slide 109

Slide 109 text

openapi-generator openapi-generator generate \ --input-spec remote.json \ --generator-name elixir \ --additional-properties packageName=remote \ --output ./remote-elixir-sdk 80

Slide 110

Slide 110 text

# cd ./remote-elixir-sdk # mix deps.get # iex -S mix alias RemoteAPI.Api.Countries alias RemoteAPI.Connection token = System.get_env("ACCESS_TOKEN") conn = Connection.new with {:ok, %{data: data}} <- Countries.get_index_holiday(conn, "Bearer #{token}", "BRA", "2024"), holidays <- Enum.take(data, -5) do for holiday <- holidays do IO.puts("* #{holiday.observed_day} - #{holiday.name}") end end # * 2024-10-12 - Our Lady of Aparecida (Nossa Senhora Aparecida) # * 2024-11-02 - Day of the Dead (Dia de Finados) # * 2024-11-15 - Proclamation of the Republic (Proclamação da República) # * 2024-11-20 - Black Awareness Day (Dia da Consciência Negra) # * 2024-12-25 - Christmas Day (Natal) 81

Slide 111

Slide 111 text

82

Slide 112

Slide 112 text

openapi-generator (e todos os outros) 83

Slide 113

Slide 113 text

openapi-generator (e todos os outros) • entidades & documentação 83

Slide 114

Slide 114 text

openapi-generator (e todos os outros) • entidades & documentação • templates/extensões 83

Slide 115

Slide 115 text

openapi-generator (e todos os outros) • entidades & documentação • templates/extensões • ~80% código gerado, ~20% manual 83

Slide 116

Slide 116 text

openapi-generator (e todos os outros) • entidades & documentação • templates/extensões • ~80% código gerado, ~20% manual • (testes, exemplos, etc) 83

Slide 117

Slide 117 text

tremendous-rewards/tremendous-node tremendous-rewards/tremendous-ruby tremendous-rewards/tremendous-python 84

Slide 118

Slide 118 text

tremendous-node import { Configuration, CreateOrderRequest, Environments, OrdersApi, } from "tremendous"; const configuration = new Configuration({ basePath: Environments.production, accessToken: "YOUR-PRODUCTION-TOKEN", }); const orders = new OrdersApi(configuration); const params: CreateOrderRequest = { /* ... */ }; const { data } = await orders.createOrder(params); console.log(`Order created! ID: ${data.order.id}`); 85

Slide 119

Slide 119 text

tremendous-python from tremendous import Configuration, ApiClient, TremendousApi configuration = Configuration( server_index=Configuration.Environment["production"], access_token="YOUR-PRODUCTION-TOKEN" ) client = TremendousApi(ApiClient(configuration)) params = CreateOrderRequest( # ... ) response = client.create_order(request) print("Order created! ID: %s" % response.order.id) 86

Slide 120

Slide 120 text

recap 87

Slide 121

Slide 121 text

recap • OpenAPI <3 HTTP/REST 87

Slide 122

Slide 122 text

recap • OpenAPI <3 HTTP/REST • yaml/json/#{sua linguagem aqui} 87

Slide 123

Slide 123 text

recap • OpenAPI <3 HTTP/REST • yaml/json/#{sua linguagem aqui} • útil para documentação, testes e dev 87

Slide 124

Slide 124 text

recap • OpenAPI <3 HTTP/REST • yaml/json/#{sua linguagem aqui} • útil para documentação, testes e dev • 80% tooling, 20% você 87

Slide 125

Slide 125 text

recap • OpenAPI <3 HTTP/REST • yaml/json/#{sua linguagem aqui} • útil para documentação, testes e dev • 80% tooling, 20% você • melhor do que fazer na mão 87

Slide 126

Slide 126 text

Obrigado! 88