Skip to content

jtreminio/oseg

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

OSEG - OpenAPI SDK Example Generator

Create (almost) ready to use SDK snippets for SDKs generated using openapi-generator using example data from your OpenAPI spec.

Examples

Several more examples can be found at jtreminio/oseg-examples.

Using the OpenAPITools/openapi-generator petstore.yaml definition, we can generate SDK examples from the addPet endpoint.

OpenAPI spec:

    post:
      tags:
        - pet
      summary: Add a new pet to the store
      description: ''
      operationId: addPet
      consumes:
        - application/json
        - application/xml
      produces:
        - application/xml
        - application/json
      parameters:
        - in: body
          name: body
          description: Pet object that needs to be added to the store
          required: true
          schema:
            $ref: '#/definitions/Pet'
      responses:
        '405':
          description: Invalid input
      security:
        - petstore_auth:
            - 'write:pets'
            - 'read:pets'

Generated SDK examples:

using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;

using Org.OpenAPITools.Api;
using Org.OpenAPITools.Client;
using Org.OpenAPITools.Model;

namespace OSEG.PetStoreExamples;

public class AddPetExample
{
    public static void Run()
    {
        var config = new Configuration();
        config.AccessToken = "YOUR_ACCESS_TOKEN";

        var category = new Category(
            id: 12345,
            name: "Category_Name"
        );

        var tags1 = new Tag(
            id: 12345,
            name: "tag_1"
        );

        var tags2 = new Tag(
            id: 98765,
            name: "tag_2"
        );

        var tags = new List<Tag>
        {
            tags1,
            tags2,
        };

        var pet = new Pet(
            name: "My pet name",
            photoUrls: [
                "https://example.com/picture_1.jpg",
                "https://example.com/picture_2.jpg",
            ],
            id: 12345,
            status: Pet.StatusEnum.Available,
            category: category,
            tags: tags
        );

        try
        {
            var response = new PetApi(config).AddPet(
                pet: pet
            );

            Console.WriteLine(response);
        }
        catch (ApiException e)
        {
            Console.WriteLine("Exception when calling PetApi#AddPet: " + e.Message);
            Console.WriteLine("Status Code: " + e.ErrorCode);
            Console.WriteLine(e.StackTrace);
        }
    }
}
package oseg.petstore_examples;

import org.openapitools.client.ApiException;
import org.openapitools.client.Configuration;
import org.openapitools.client.api.*;
import org.openapitools.client.auth.*;
import org.openapitools.client.JSON;
import org.openapitools.client.model.*;

import java.io.File;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class AddPetExample
{
    public static void main(String[] args)
    {
        var config = Configuration.getDefaultApiClient();
        ((HttpBearerAuth) config.getAuthentication("petstore_auth")).setBearerToken("YOUR_ACCESS_TOKEN");

        var category = new Category();
        category.id(12345L);
        category.name("Category_Name");

        var tags1 = new Tag();
        tags1.id(12345L);
        tags1.name("tag_1");

        var tags2 = new Tag();
        tags2.id(98765L);
        tags2.name("tag_2");

        var tags = new ArrayList<Tag>(List.of (
            tags1,
            tags2
        ));

        var pet = new Pet();
        pet.name("My pet name");
        pet.photoUrls(List.of (
            "https://example.com/picture_1.jpg",
            "https://example.com/picture_2.jpg"
        ));
        pet.id(12345L);
        pet.status(Pet.StatusEnum.AVAILABLE);
        pet.category(category);
        pet.tags(tags);

        try
        {
            var response = new PetApi(config).addPet(
                pet
            );

            System.out.println(response);
        } catch (ApiException e) {
            System.err.println("Exception when calling PetApi#addPet");
            System.err.println("Status code: " + e.getCode());
            System.err.println("Reason: " + e.getResponseBody());
            System.err.println("Response headers: " + e.getResponseHeaders());
            e.printStackTrace();
        }
    }
}
package oseg.petstore_examples

import org.openapitools.client.infrastructure.*
import org.openapitools.client.apis.*
import org.openapitools.client.models.*

import java.io.File
import java.time.LocalDate
import java.time.OffsetDateTime
import kotlin.collections.ArrayList
import kotlin.collections.List
import kotlin.collections.Map
import com.squareup.moshi.adapter

@ExperimentalStdlibApi
class AddPetExample
{
    fun addPet()
    {
        ApiClient.accessToken = "YOUR_ACCESS_TOKEN"

        val category = Category(
            id = 12345,
            name = "Category_Name",
        )

        val tags1 = Tag(
            id = 12345,
            name = "tag_1",
        )

        val tags2 = Tag(
            id = 98765,
            name = "tag_2",
        )

        val tags = arrayListOf<Tag>(
            tags1,
            tags2,
        )

        val pet = Pet(
            name = "My pet name",
            photoUrls = listOf (
                "https://example.com/picture_1.jpg",
                "https://example.com/picture_2.jpg",
            ),
            id = 12345,
            status = Pet.Status.available,
            category = category,
            tags = tags,
        )

        try
        {
            val response = PetApi().addPet(
                pet = pet,
            )

            println(response)
        } catch (e: ClientException) {
            println("4xx response calling PetApi#addPet")
            e.printStackTrace()
        } catch (e: ServerException) {
            println("5xx response calling PetApi#addPet")
            e.printStackTrace()
        }
    }
}
<?php

namespace OSEG\PetStoreExamples;

require_once __DIR__ . '/../vendor/autoload.php';

use SplFileObject;
use OpenAPI;

$config = OpenAPI\Client\Configuration::getDefaultConfiguration();
$config->setAccessToken("YOUR_ACCESS_TOKEN");

$category = (new OpenAPI\Client\Model\Category())
    ->setId(12345)
    ->setName("Category_Name");

$tags_1 = (new OpenAPI\Client\Model\Tag())
    ->setId(12345)
    ->setName("tag_1");

$tags_2 = (new OpenAPI\Client\Model\Tag())
    ->setId(98765)
    ->setName("tag_2");

$tags = [
    $tags_1,
    $tags_2,
];

$pet = (new OpenAPI\Client\Model\Pet())
    ->setName("My pet name")
    ->setPhotoUrls([
        "https://example.com/picture_1.jpg",
        "https://example.com/picture_2.jpg",
    ])
    ->setId(12345)
    ->setStatus(OpenAPI\Client\Model\Pet::STATUS_AVAILABLE)
    ->setCategory($category)
    ->setTags($tags);

try {
    $response = (new OpenAPI\Client\Api\PetApi(config: $config))->addPet(
        pet: $pet,
    );

    print_r($response);
} catch (OpenAPI\Client\ApiException $e) {
    echo "Exception when calling PetApi#addPet: {$e->getMessage()}";
}
import json
from datetime import date, datetime
from pprint import pprint

from openapi_client import ApiClient, ApiException, Configuration, api, models

configuration = Configuration(
    access_token="YOUR_ACCESS_TOKEN",
)

with ApiClient(configuration) as api_client:
    category = models.Category(
        id=12345,
        name="Category_Name",
    )

    tags_1 = models.Tag(
        id=12345,
        name="tag_1",
    )

    tags_2 = models.Tag(
        id=98765,
        name="tag_2",
    )

    tags = [
        tags_1,
        tags_2,
    ]

    pet = models.Pet(
        name="My pet name",
        photoUrls=[
            "https://example.com/picture_1.jpg",
            "https://example.com/picture_2.jpg",
        ],
        id=12345,
        status="available",
        category=category,
        tags=tags,
    )

    try:
        response = api.PetApi(api_client).add_pet(
            pet=pet,
        )

        pprint(response)
    except ApiException as e:
        print("Exception when calling PetApi#add_pet: %s\n" % e)
require "json"
require "openapi_client"

OpenApiClient.configure do |config|
    config.access_token = "YOUR_ACCESS_TOKEN"
end

category = OpenApiClient::Category.new
category.id = 12345
category.name = "Category_Name"

tags_1 = OpenApiClient::Tag.new
tags_1.id = 12345
tags_1.name = "tag_1"

tags_2 = OpenApiClient::Tag.new
tags_2.id = 98765
tags_2.name = "tag_2"

tags = [
    tags_1,
    tags_2,
]

pet = OpenApiClient::Pet.new
pet.name = "My pet name"
pet.photo_urls = [
    "https://example.com/picture_1.jpg",
    "https://example.com/picture_2.jpg",
]
pet.id = 12345
pet.status = "available"
pet.category = category
pet.tags = tags

begin
    response = OpenApiClient::PetApi.new.add_pet(
        pet,
    )

    p response
rescue OpenApiClient::ApiError => e
    puts "Exception when calling PetApi#add_pet: #{e}"
end
import * as fs from 'fs';
import api from "openapi_client"
import models from "openapi_client"

const apiCaller = new api.PetApi();
apiCaller.accessToken = "YOUR_ACCESS_TOKEN";

const category = new models.Category();
category.id = 12345;
category.name = "Category_Name";

const tags1 = new models.Tag();
tags1.id = 12345;
tags1.name = "tag_1";

const tags2 = new models.Tag();
tags2.id = 98765;
tags2.name = "tag_2";

const tags = [
  tags1,
  tags2,
];

const pet = new models.Pet();
pet.name = "My pet name";
pet.photoUrls = [
  "https://example.com/picture_1.jpg",
  "https://example.com/picture_2.jpg",
];
pet.id = 12345;
pet.status = models.Pet.StatusEnum.Available;
pet.category = category;
pet.tags = tags;

apiCaller.addPet(
  pet,
).then(response => {
  console.log(response.body);
}).catch(error => {
  console.log("Exception when calling PetApi#addPet:");
  console.log(error.body);
});

Where does the example data come from?

Example data used to generate the code snippets can come from a number of places:

OSEG currently generates one code snippet file per requestBody definition, but only a single example per parameters definition.

In other words, you can generate multiple examples of the same operation/endpoint by having multiple requestBody definitions. Each generate example will have different data embedded within it. However, since parameters is tied to a single operation, the same data will apply to any and all requestBody definitions.

If you use a custom JSON file with examples you can define as many examples per endpoint as you want, since you can define each data source separately (query, path, header, cookie, and body) per endpoint.

Adding more SDKs

Examples are generated using (fairly) simple Jinja templates. Adding a new SDK requires a few steps:

  1. Create the Generator
  2. Create the Jinja template (Python example)

How to run

The entrypoint to this project is run.py. It currently supports two commands:

config-help

Prints all config options available to a given generator.

Run python3 run.py config-help --help for more details.

Or, with Docker: docker container run --rm jtreminio/oseg:latest config-help --help

Show config options for the python generator:

python3 run.py config-help -g python

Or, with Docker:

docker container run --rm jtreminio/oseg:latest config-help -g python

generate

Generates the SDK snippets and writes to specified location.

Run python3 run.py generate --help for more details.

Run with a local config file:

python3 run.py generate \
    -i openapi.yaml \
    -o python/src \
    --config-file config-python.yaml \
    --example-data-file example_data.json

Or, with Docker:

docker container run --rm -v $PWD:/app jtreminio/oseg:latest generate \
    -i openapi.yaml \
    -o python/src \
    --config-file config-python.yaml \
    --example-data-file example_data.json

Run with inline config values. These config values can be seen by running python3 run.py config-help -g {generator}:

python3 run.py generate \
    -i openapi.yaml \
    -o python/src \
    --generator-name python \
    --config '{
      "packageName": "openapi_client",
      "oseg.propertyNamingConvention": "camel_case",
      "oseg.security.api_key.api_key": "YOUR_API_KEY",
      "oseg.security.petstore_auth.access_token": "YOUR_ACCESS_TOKEN"
    }' \
    --example-data-file example_data.json

Or, with Docker:

docker container run --rm -v $PWD:/app jtreminio/oseg:latest generate \
    -i openapi.yaml \
    -o python/src \
    --generator-name python \
    --config '{
      "packageName": "openapi_client",
      "oseg.propertyNamingConvention": "camel_case",
      "oseg.security.api_key.api_key": "YOUR_API_KEY",
      "oseg.security.petstore_auth.access_token": "YOUR_ACCESS_TOKEN"
    }' \
    --example-data-file example_data.json

Tests

See tests directory.

Feature support

The aim of this project is to cover the most common use-cases in an OpenAPI spec.

Supported

  • Requests with and without formdata: openapi-generator will create a different interface if an endpoint's content-type has formdata (multipart/form-data or application/x-www-form-urlencoded) and everything else
  • Discriminators with allOf

Issues

The following are issues with the SDK code generated by openapi-generator.

  • allOf without a discriminator: Depending on the openapi-generator generator used, the generated SDK may be completely broken
    • typescript-node SDK code is generated broken by openapi-generator
    • Other SDKs currently supported by OSEG will usually just use a generic object type
  • anyOf
    • typescript-node generated SDK does not set data
    • php generated SDK does not set data
    • Other SDKs currently supported by OSEG generate a class
  • oneOf
    • typescript-node generated SDK does not set data
    • php generated SDK does not set data
    • Other SDKs currently supported by OSEG generate a class
  • Multiple values in type definition. New in OpenAPI 3.1 type can now be an array of supported types
    • c#, java, ruby Generate a class
    • python generates a class but does not use it, types property as the first value in type
    • php generates a class, data not set
    • typescript-node generates a class, data not set
  • openapi-generator generates a class for inline schema with type=object. If two or more Operations share identical inline schema definitions, openapi-generator will only generate a class for one of the Operations, and all Operations will reference this class. The name of the class depends on which Operation is read first by openapi-generator, but it may not reflect the first definition within the OAS file and thus is impossible for OSEG to know the name of the class ahead of time.