Generating Taxi from source
Instantiate Taxi from its original source
Java & Kotlin
You can generate Taxi models and services from Java and Kotlin.
@DataType & @Namespace
@Namespace("demo")
@DataType
data class Person(...) // generates demo.Person
@DataType("demo.Person")
data class Person(...) // generates demo.Person
@DataType
is the main annotation to indicate that a class should have a Taxi schema generated.
Namespaces may be defined either through the @Namespace("foo.bar")
annotation, or within a @DataType
directly.
If the namespace isn't provided through either approach, the package name is used.
Fields on types are exported. By default, the corresponding primitive type in Taxi is used. However, the output type can be customized by specifying the type in a @DataType
annotation on the field, which will result in a Type alias within Taxi being generates.
@DataType("demo.Client")
data class Client(@field:DataType("demo.ClientId") val clientId: String,
val clientName: String,
@field:DataType("isic.uk.SIC2008") val sicCode: String
)
TaxiGenerator().forClasses(Client::class.java).generateAsStrings()
// Generates:
namespace demo {
type Client {
clientId : ClientId as String
clientName : String
sicCode : isic.uk.SIC2008
}
}
namespace isic.uk {
type alias SIC2008 as String
}
Support for Kotlin Type Aliases
Type aliases can be directly annotated with the @DataType
annotation.
package com.foo.bar
@DataType
data class Bus(val passengers: Passengers)
@DataType
typealias Passengers = List<Person>
@DataType("foo.Person")
data class Person(val name: PersonName)
@DataType("foo.PersonName")
typealias PersonName = String
TypeAliasRegister.registerPackage("com.foo.bar")
val taxiDef = TaxiGenerator().forClasses(Bus::class.java).generateAsStrings()
// Generates:
model Bus {
passengers : Passengers
}
model Person {
name : PersonName
}
type PersonName inherits String
type alias Passengers as Person[]
If using the @DataType
against a Kotlin type alias, you need to declare the package(s) at startup.
This is as simple as:
import lang.taxi.generators.kotlin.TypeAliasRegister
TypeAliasRegister.registerPackage("com.foo.bar")
Any annotated type aliases in the provided package will be discovered and have their names generated correctly.
Constraints
Constraints are supported via the @Constraint
annotation.
@DataType("vyne.Money")
data class Money(val amount: BigDecimal, val currency: String)
@ParameterType
@DataType("vyne.SomeRequest")
data class SomeRequest(
@Constraint("currency = 'USD'")
val amount: Money)
TaxiGenerator().forClasses(Money::class.java, SomeRequest::class.java).generateAsStrings()
// Generates:
namespace vyne {
parameter type SomeRequest {
amount : Money(currency = "USD")
}
type Money {
amount : Decimal
currency : String
}
}
Parameter Type
Parameter types are indicated through the @ParameterType
annotation.
Services & Operations
Services and Operations are exposed via the @Service
and @Operation
annotations respectively.
@Service("taxi.example.PersonService")
class MyService {
@Operation
fun findPerson(@DataType("taxi.example.PersonId") personId: String): Person {
}
}
TaxiGenerator().forClasses(MyService::class.java, Person::class.java).generateAsStrings()
// Generates:
namespace taxi.example {
type Person {
personId : PersonId as String
}
service PersonService {
operation findPerson(PersonId) : Person
}
}
Operations may customize the generated name of operation parameters through the @Parameter
annotation:
@Operation
fun findPerson(@Parameter(name = "personId") id: String): Person ...
Service constraints & contracts
Constraints on parameters are defined by adding constraints
to the @Parameter
annotation.
Contracts for operations are defined on the method, using the @ResponseContract
annotation.
@Service("taxi.example.MoneyService")
class MyService {
@Operation
@ResponseContract(basedOn = "source",
constraints = ResponseConstraint("currency = targetCurrency")
)
fun convertRates(@Parameter(constraints = Constraint("currency = 'GBP'")) source: Money, @Parameter(name = "targetCurrency") targetCurrency: String): Money {
TODO("Not a real service")
}
}
TaxiGenerator().forClasses(MyService::class.java)
// Generates
namespace taxi.example
type Money {
currency : Currency as String
value : MoneyAmount as Decimal
}
service MoneyService {
operation convertRates( Money( currency = "GBP" ),
targetCurrency : String ) : Money( from source, currency = targetCurrency )
}
Taxi Generator customisation & extensions
Taxi's generator can be customized, adding additional annotations or modfiying the tree structure before source is output.
The most common scenario here is to provide extensions to modify how services are generated. However, you can inject an entirely separate TypeMapper
and ServiceMapper
for complete control over the generation process. Take a look at TaxiGenerator to get started.
ServiceMapperExtension and OperationMapperExtension provide the ability to modify the way services are generated, injecting custom metadata (eg., to describe HTTP operations, or how to subscribe to a message bus).
The SpringMVC extensions (discussed below) give a good example of how to leverage this.
Spring MVC Extensions
java2taxi ships with extensions for Spring MVC, to add metadata about how services are discovered and invoked. They add the following features:
- Support for mapping
@GetMapping
/@PostMapping
etc to anHttpOperation()
annotation on the output.- All HTTP methods are supported
- Path variable substitution is supported
- Support for mapping springs
@RequestBody
annotation to a corresponding@RequestBody
annotation on the generated Taxi source. - Support for integration with service discovery from Spring Cloud's
DiscoveryClient
abstraction
Enable the extensions by configuring the TaxiGenerator
as follows:
val taxiGenerator = TaxiGenerator(serviceMapper = DefaultServiceMapper(
operationExtensions = listOf(SpringMvcHttpOperationExtension()),
serviceExtensions = listOf(SpringMvcHttpServiceExtension(ServiceDiscoveryAddressProvider("mockService")))
))
Example usage:
@RequestMapping("/costs")
@Service("vyne.demo.CreditCostService")
class CreditCostService {
@Operation
@GetMapping("/interestRates/{clientId}")
fun getInterestRate(@PathVariable("clientId") @DataType("vyne.demo.ClientId") clientId: String): BigDecimal = BigDecimal.ONE
// Back off, REST snobs. Method names are here for testing.
@PostMapping("/{clientId}/doCalculate")
@Operation
fun calculateCreditCosts(@PathVariable("clientId") @DataType("vyne.demo.ClientId") clientId: String, @RequestBody request: CreditCostRequest): CreditCostResponse = CreditCostResponse("TODO")
}
taxiGenerator.forClasses(CreditCostService::class.java).generateAsStrings()
// Generates:
namespace vyne.demo {
type alias ClientId as String
type CreditCostRequest {
deets : String
}
type CreditCostResponse {
stuff : String
}
@ServiceDiscoveryClient(serviceName = "mockService")
service CreditCostService {
@HttpOperation(method = "GET" , url = "/costs/interestRates/{vyne.demo.ClientId}")
operation getInterestRate( ClientId ) : Decimal
@HttpOperation(method = "POST" , url = "/costs/{vyne.demo.ClientId}/doCalculate")
operation calculateCreditCosts( ClientId, @RequestBody CreditCostRequest ) : CreditCostResponse
}
}
The tests give the best example of usage.
Maven / Gradle
The binaries are available on Bintray. Be sure to add the repository:
<repositories>
<repository>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>bintray-taxi-lang-releases</id>
<name>bintray</name>
<url>https://dl.bintray.com/taxi-lang/releases</url>
</repository>
</repositories>
Then grab the artifacts:
<!-- To generate Taxi, you need java2taxi -->
<dependency>
<groupId>lang.taxi</groupId>
<artifactId>java2taxi</artifactId>
<version>0.1.0</version>
</dependency>
<!-- To annotate your models, you need the annotations package -->
<dependency>
<groupId>lang.taxi</groupId>
<artifactId>taxi-annotations</artifactId>
<version>0.1.0</version>
</dependency>
Swagger / OpenApi
It's possible to convert a swagger document to Taxi, using the TaxiGenerator
from the openApi
package.
This is especially useful if you want to take advantage of Taxi's type extensions, to mix in annotations. (eg., persistence or validation).
Grab the taxi generator from maven:
<dependency>
<groupId>lang.taxi</groupId>
<artifactId>swagger2taxi</artifactId>
<version>0.1.0</version>
</dependency>
Then run the converter as follows:
(Note, this example loads source from this reference example from the OpenApi project -
// The actual swagger source is omitted for brevity
val source = IOUtils.toString(URI.create("https://gitlab.com/taxi-lang/taxi-lang/raw/master/swagger2taxi/src/test/resources/openApiSpec/v2.0/yaml/petstore-simple.yaml"))
TaxiGenerator().generateAsStrings(source, "vyne.openApi")
// generated:
namespace vyne.openApi {
model NewPet {
name : String
tag : String?
}
model Pet inherits NewPet {
id : Int
}
model ErrorModel {
code : Int
message : String
}
service PetsService {
[[ Returns all pets from the system that the user has access to ]]
@HttpOperation(method = "GET" , url = "http://petstore.swagger.io/api/pets")
operation findPets( tags : String, limit : Int ) : Pet[]
[[ Creates a new pet in the store. Duplicates are allowed ]]
@HttpOperation(method = "POST" , url = "http://petstore.swagger.io/api/pets")
operation addPet( @RequestBody pet : NewPet ) : Pet
}
service PetsIdService {
[[ Returns a user based on a single ID, if the user does not have access to the pet ]]
@HttpOperation(method = "GET" , url = "http://petstore.swagger.io/api/pets/{id}")
operation findPetById( @PathVariable(value = "id") id : Int ) : Pet
[[ deletes a single pet based on the ID supplied ]]
@HttpOperation(method = "DELETE" , url = "http://petstore.swagger.io/api/pets/{id}")
operation deletePet( @PathVariable(value = "id") id : Int )
}
}
OpenAPI x-taxi-type Extension
Like any generator, TaxiGenerator
can only do its best with what is provided.
Both Swagger and OpenAPI specifications rely heavily on unnamed schemas.
TaxiGenerator
has to turn these into types, and so has to invent names for
them.
In addition TaxiGenerator
cannot know about your existing Taxi taxonomy, nor
could it reliably match schemas from the OpenAPI specification with types in
that taxonomy without further help.
To solve this, TaxiGenerator
supports an OpenAPI Extension
x-taxi-type
, allowing you to specify the name, fully qualified or otherwise,
of the type, and whether it should be generated, or assumed to exist.
The x-taxi-type
extension can be applied to the schema of parameters, request
bodies, and response contents of OpenAPI operations.
The x-taxi-type
extension can be applied in both json
and yaml
OpenAPI
specification documents. For example:
OpenAPI JSON specification with x-taxi-type
OpenAPI YAML specification with x-taxi-type
Resulting generated taxi
Specification
- Any OpenAPI schema object may contain an
x-taxi-type
object at the top level - An
x-taxi-type
object must contain aname
property of typestring
- If the value of the name is unqualified, the generator considers it to be in
the default namespace provided as an argument to
generateAsStrings
- The generator will use that name as the name of the type in preference to the name the generator would otherwise have generated.
- An
x-taxi-type
object may contain acreate
property of typeboolean
- The
create
property defaults to true if the schema would generate amodel
(i.e., it either has properties or is composed via anallOf
) - The
create
property defaults to false if the schema would generate atype
(i.e., it neither has properties nor is composed via anallOf
)
Examples
openapi: "3.0.0"
info:
version: 1.0.0
title: Swagger Petstore
components:
schemas:
Person:
type: object
properties:
name:
x-taxi-type:
name: org.other.Name
type: string
dob:
x-taxi-type:
name: DateOfBirth
create: true
type: string
format: date
address:
x-taxi-type:
name: org.other.Address
create: false
properties:
street:
type: string
job:
x-taxi-type:
name: Career
properties:
title:
type: string
paths: {}
generates
namespace vyne.openApi {
type DateOfBirth inherits Date
model Career {
title : String?
}
model Person {
name : org.other.Name?
dob : DateOfBirth?
address : org.other.Address?
job : Career?
}
}