https://github.com/paysera/util-raml-code-generator
Generates PHP or JS RESTful Client from RAML API definition
https://github.com/paysera/util-raml-code-generator
code-generator raml raml-codegen raml-tooling
Last synced: 9 months ago
JSON representation
Generates PHP or JS RESTful Client from RAML API definition
- Host: GitHub
- URL: https://github.com/paysera/util-raml-code-generator
- Owner: paysera
- Created: 2017-02-03T12:27:24.000Z (over 9 years ago)
- Default Branch: master
- Last Pushed: 2025-07-21T08:47:09.000Z (11 months ago)
- Last Synced: 2025-07-21T10:28:03.562Z (11 months ago)
- Topics: code-generator, raml, raml-codegen, raml-tooling
- Language: PHP
- Homepage:
- Size: 70.8 MB
- Stars: 13
- Watchers: 7
- Forks: 21
- Open Issues: 19
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
Awesome Lists containing this project
README
# util-raml-code-generator
[![Latest Version][ico-version]][link-releases]
[![Build Status][ico-travis]][link-travis]
`util-raml-code-generator` generates code packages from specified RAML definition.
Currently this utility can:
* Automatically generate and release:
* `PHP REST API client`
* `Javascript REST API client`
* Generate `Symfony Api Bundle`
## Requirements
- `PHP >=7.4`
- `Composer <= 2.2.9`, higher versions not yet supported, work in progress
## Installation
* Clone repository and run `composer install`
# Table of contents
1. [RAML structure](#raml-structure)
1. [api.raml](#raml/category/api.raml)
1. [types/category.raml](#raml/category/types/category.raml)
1. [types/category-result.raml](#raml/category/types/category-result.raml)
1. [traits/category-filter.raml](#raml/category/traits/category-filter.raml)
1. [Custom annotations](#custom-annotations)
1. [`(generator_method_name_override)`](#--generator-method-name-override--)
1. [Generate and publish clients](#generate-and-publish-clients)
1. [config.json format](#config.json-format)
1. [Generate Javascript REST client](#generate-javascript-rest-client)
1. [File tree](#javascript-client-file-tree)
1. [src/entity/Category.js](#src/entity/category.js)
1. [src/entity/CategoryFilter.js](#src/entity/categoryfilter.js)
1. [src/entity/CategoryResult.js](#src/entity/categoryresult.js)
1. [src/service/createClient.js](#src/service/createclient.js)
1. [src/service/CategoryClient.js](#src/service/categoryclient.js)
1. [Generate PHP REST client](#generate-php-rest-client)
1. [File tree](#php-client-file-tree)
1. [src/CategoryClient.php](#src/categoryclient.php)
1. [src/ClientFactory.php](#src/clientfactory.php)
1. [src/Entity/Category.php](#src/entity/category.php)
1. [src/Entity/CategoryFilter.php](#src/entity/categoryfilter.php)
1. [src/Entity/CategoryResult.php](#src/entity/categoryresult.php)
1. [Generate Symfony Bundle](#generate-symfony-bundle)
## RAML structure
Lets have following `RAML` structure:
```
raml
└───category
| api.raml
|
├───traits
| category-filter.raml
|
├───types
| category.raml
| category-result.raml
```
With following contents:
#### raml/category/api.raml
```yaml
#%RAML 1.0
title: Category API
version: v1
baseUri: https://example.com/category/rest/v1
mediaType: application/json
protocols: [ HTTPS ]
uses:
Paysera: https://raw.githubusercontent.com/paysera/lib-raml-common/master/rest.raml
types:
Category: !include types/category.raml
CategoryResult: !include types/category-result.raml
traits:
CategoryFilter: !include traits/category-filter.raml
/categories:
get:
description: Get categories list
is:
- CategoryFilter
post:
description: Create category
body:
application/json:
type: Category
responses:
200:
body:
application/json:
type: CategoryResult
/{id}:
uriParameters:
id:
type: string
put:
description: Update category
body:
application/json:
type: Category
responses:
200:
body:
application/json:
type: Category
delete:
description: Delete category
/enable:
put:
description: Enable category
responses:
200:
body:
application/json:
type: Category
/disable:
put:
description: Disable category
responses:
200:
body:
application/json:
type: Category
```
#### raml/category/types/category.raml
```yaml
#%RAML 1.0 DataType
type: object
displayName: Category object
properties:
id:
type: string
required: false
description: Object id
parent_id:
type: string
required: false
description: Category parent id
name:
type: string
required: true
description: Category title
status:
type: string
enum: ["active", "inactive"]
required: false
description: Category status
```
#### raml/category/traits/category-filter.raml
```yaml
#%RAML 1.0 Trait
usage: Used where Category filtering is provided
description: Category Result filtering
is:
- Paysera.Filter
queryParameters:
parent_id:
displayName: parent_id
type: string
minLength: 1
example: 123456
required: false
```
#### raml/category/types/category-result.raml
```yaml
#%RAML 1.0 DataType
type: Paysera.Result
displayName: Category Result object
properties:
items:
required: true
type: array
items:
type: Category
```
## Custom annotations
### `(generator_method_name_override)`
Use this to override method name (for any generated client or bundle).
For example:
```
#%RAML 1.0
title: Custom
version: 1.0
baseUri: https://my-api.example.com/rest/v1
annotationTypes:
generator_method_name_override: string
/something:
get:
(generator_method_name_override): customNameForMethod
responses:
204: ~
```
## Generate and publish clients
1. Run `bin/console release:clients {path_to_config}`.
* `path_to_config` path to `config.json` file.
This command will
1. clone repository
1. generate clients from `raml`
1. update changelog
1. show you the difference between current and generated files
1. generate dist files for javascript
1. create a commit
1. push commit
1. create tag
#### config.json format
```json
{
"SomeApiName": {
"raml_file": "path/to/api.raml",
"clients": {
"javascript": {
"repository": "ssh://some.hostname.com/source/js-some-monorepo.git/packages/some-api-client",
"library_name": "@vendor/some-api-client",
"client_name": "SomeApiClient"
},
"php": {
"repository": "ssh://some.hostname.com/source/some-api-client-dedicated-repo.git",
"library_name": "vendor/lib-some-api-client",
"namespace": "Vendor\\Client\\SomeApiClient"
}
}
}
}
```
## Generate Javascript REST client
1. Run `bin/console js-generator:package {path_to_raml} {output_dir} {client_name} --library_name={library_name}`.
* `path_to_raml` path to `raml` file.
* `output_dir` directory where to put generated files.
* `client_name` is the name of your main javascript client.
* `library_name` is optional name for generated library, i.e. `@paysera/some-api-client`
1. Check the dumped output to `{output_dir}` directory.
#### Javascript client file tree
In `output_dir` you should expect these files generated:
```
| .babelrc.js
| .eslintrc.js
| .gitignore
| package.json
| README.md
| webpack.config.js
|
└───src
| index.js
| angular.module.js
|
├───entity
| Category.js
| CategoryFilter.js
| CategoryResult.js
|
├───service
| CategoryClient.js
| createClient.js
```
With following contents of services and entities `src` directory:
#### src/entity/Category.js
```javascript
import { Entity } from '@paysera/http-client-common';
class Category extends Entity {
constructor(data = {}) {
super(data);
}
/**
* @return {string}|null
*/
getId() {
return this.get('id');
}
/**
* @param {string} id
*/
setId(id) {
this.set('id', id);
}
/**
* @return {string}|null
*/
getParentId() {
return this.get('parent_id');
}
/**
* @param {string} parentId
*/
setParentId(parentId) {
this.set('parent_id', parentId);
}
/**
* @return {string}
*/
getName() {
return this.get('name');
}
/**
* @param {string} name
*/
setName(name) {
this.set('name', name);
}
/**
* @return {string}|null
*/
getStatus() {
return this.get('status');
}
/**
* @param {string} status
*/
setStatus(status) {
this.set('status', status);
}
}
export default Category;
```
#### src/entity/CategoryFilter.js
```javascript
import { Filter } from '@paysera/http-client-common';
class CategoryFilter extends Filter {
/**
* @return {string}|null
*/
getParentId() {
return this.get('parent_id');
}
/**
* @param {string} parentId
*/
setParentId(parentId) {
this.set('parent_id', parentId);
}
}
export default CategoryFilter;
```
#### src/entity/CategoryResult.js
```javascript
import Category from './Category';
import { Result } from '@paysera/http-client-common';
/* eslint class-methods-use-this: ["error", { "exceptMethods": ["createItem"] }] */
class CategoryResult extends Result {
/**
* @param {Array} data
* @returns {Category}
*/
createItem(data) {
return new Category(data);
}
}
export default CategoryResult;
```
#### src/service/createClient.js
```javascript
import { createClient } from '@paysera/http-client-common';
import CategoryClient from './CategoryClient';
/**
* @param {string} baseURL
* @param {[]|null} middleware
* @param {object} options
*
* @returns {CategoryClient}
*/
/* eslint import/prefer-default-export: ["off"] */
export const createAClient = ({
baseURL = 'https://example.com/category/rest/v1/',
middleware = null,
options = {}
}) => {
const defaultUrlParameters = {};
if (Object.prototype.hasOwnProperty.call(options, 'urlParameters')) {
const { urlParameters } = options;
for (let [key, value] of Object.entries(defaultUrlParameters)) {
if (!Object.prototype.hasOwnProperty.call(urlParameters, key)) {
urlParameters[key] = value;
}
}
options.urlParameters = urlParameters;
}
return new CategoryClient(
createClient({
baseURL,
middleware,
options
})
)
};
```
#### src/service/CategoryClient.js
```javascript
import { createRequest, ClientWrapper } from '@paysera/http-client-common';
import Category from '../entity/Category';
import CategoryFilter from '../entity/CategoryFilter';
import CategoryResult from '../entity/CategoryResult';
class CategoryClient {
/**
* @param {ClientWrapper} client
*/
constructor(client) {
this.client = client;
}
/**
* Enable category
* PUT /categories/{id}/enable
*
* @param {string} id
* @return {Promise.}
*/
enableCategory(id) {
const request = createRequest(
'PUT',
`categories/${encodeURIComponent(id)}/enable`,
null,
);
return this.client
.performRequest(request)
.then(data => new Category(data));
}
/**
* Disable category
* PUT /categories/{id}/disable
*
* @param {string} id
* @return {Promise.}
*/
disableCategory(id) {
const request = createRequest(
'PUT',
`categories/${encodeURIComponent(id)}/disable`,
null,
);
return this.client
.performRequest(request)
.then(data => new Category(data));
}
/**
* Update category
* PUT /categories/{id}
*
* @param {string} id
* @param {Category} category
* @return {Promise.}
*/
updateCategory(id, category) {
const request = createRequest(
'PUT',
`categories/${encodeURIComponent(id)}`,
category,
);
return this.client
.performRequest(request)
.then(data => new Category(data));
}
/**
* Delete category
* DELETE /categories/{id}
*
* @param {string} id
* @return {Promise.}
*/
deleteCategory(id) {
const request = createRequest(
'DELETE',
`categories/${encodeURIComponent(id)}`,
null,
);
return this.client
.performRequest(request)
.then(data => null);
}
/**
* Standard SQL-style Result filtering
* GET /categories
*
* @param {CategoryFilter} categoryFilter
* @return {Promise.}
*/
getCategories(categoryFilter) {
const request = createRequest(
'GET',
`categories`,
categoryFilter,
);
return this.client
.performRequest(request)
.then(data => null);
}
/**
* Create category
* POST /categories
*
* @param {Category} category
* @return {Promise.}
*/
createCategory(category) {
const request = createRequest(
'POST',
`categories`,
category,
);
return this.client
.performRequest(request)
.then(data => new CategoryResult(data, 'items'));
}
}
export default AClient;
```
## Generate PHP REST client
1. Run `bin/console php-generator:rest-client {path_to_raml} {output_dir} {namespace} --library_name={library_name}`.
* `path_to_raml` path to `raml` file.
* `output_dir` directory where to put generated files.
* `namespace` is the namespace of Client.
* `library_name` is optional name for generated library, i.e. `paysera/lib-some-api-client`
1. Check the dumped output to `{output_dir}` directory.
#### PHP Client file tree
In `output_dir` you should expect these files generated:
```
| composer.json
| README.md
|
└───src
| CategoryClient.php
| ClientFactory.php
|
├───Entity
| Category.php
| CategoryFilter.php
| CategoryResult.php
```
With following contents of `src` directory:
#### src/CategoryClient.php
```php
apiClient = $apiClient;
}
public function withOptions(array $options)
{
return new CategoryClient($this->apiClient->withOptions($options));
}
/**
* Enable category
* PUT /categories/{id}/enable
*
* @param string $id
* @return Entities\Category
*/
public function enableCategory($id)
{
$request = $this->apiClient->createRequest(
RequestMethodInterface::METHOD_PUT,
sprintf('categories/%s/enable', urlencode($id)),
null
);
$data = $this->apiClient->makeRequest($request);
return new Entities\Category($data);
}
/**
* Disable category
* PUT /categories/{id}/disable
*
* @param string $id
* @return Entities\Category
*/
public function disableCategory($id)
{
$request = $this->apiClient->createRequest(
RequestMethodInterface::METHOD_PUT,
sprintf('categories/%s/disable', urlencode($id)),
null
);
$data = $this->apiClient->makeRequest($request);
return new Entities\Category($data);
}
/**
* Update category
* PUT /categories/{id}
*
* @param string $id
* @param Entities\Category $category
* @return Entities\Category
*/
public function updateCategory($id, Entities\Category $category)
{
$request = $this->apiClient->createRequest(
RequestMethodInterface::METHOD_PUT,
sprintf('categories/%s', urlencode($id)),
$category
);
$data = $this->apiClient->makeRequest($request);
return new Entities\Category($data);
}
/**
* Delete category
* DELETE /categories/{id}
*
* @param string $id
* @return null
*/
public function deleteCategory($id)
{
$request = $this->apiClient->createRequest(
RequestMethodInterface::METHOD_DELETE,
sprintf('categories/%s', urlencode($id)),
null
);
$data = $this->apiClient->makeRequest($request);
return null;
}
/**
* Standard SQL-style Result filtering
* GET /categories
*
* @param Entities\CategoryFilter $categoryFilter
* @return null
*/
public function getCategories(Entities\CategoryFilter $categoryFilter)
{
$request = $this->apiClient->createRequest(
RequestMethodInterface::METHOD_GET,
'categories',
$categoryFilter
);
$data = $this->apiClient->makeRequest($request);
return null;
}
/**
* Create category
* POST /categories
*
* @param Entities\Category $category
* @return Entities\CategoryResult
*/
public function createCategory(Entities\Category $category)
{
$request = $this->apiClient->createRequest(
RequestMethodInterface::METHOD_POST,
'categories',
$category
);
$data = $this->apiClient->makeRequest($request);
return new Entities\CategoryResult($data, 'items');
}
}
```
#### src/ClientFactory.php
```php
apiClient = $options;
return;
}
$defaultUrlParameters = [];
$options['url_parameters'] = $this->resolveDefaultUrlParameters($defaultUrlParameters, $options);
$this->apiClient = $this->createApiClient($options);
}
public function getAClient()
{
return new CategoryClient($this->apiClient);
}
private function resolveDefaultUrlParameters(array $defaults, array $options)
{
$params = [];
if (isset($options['url_parameters'])) {
$params = $options['url_parameters'];
}
return $params + $defaults;
}
}
```
#### src/Entity/Category.php
```php
get('id');
}
/**
* @param string $id
* @return $this
*/
public function setId($id)
{
$this->set('id', $id);
return $this;
}
/**
* @return string|null
*/
public function getParentId()
{
return $this->get('parent_id');
}
/**
* @param string $parentId
* @return $this
*/
public function setParentId($parentId)
{
$this->set('parent_id', $parentId);
return $this;
}
/**
* @return string
*/
public function getName()
{
return $this->get('name');
}
/**
* @param string $name
* @return $this
*/
public function setName($name)
{
$this->set('name', $name);
return $this;
}
/**
* @return string|null
*/
public function getStatus()
{
return $this->get('status');
}
/**
* @param string $status
* @return $this
*/
public function setStatus($status)
{
$this->set('status', $status);
return $this;
}
}
```
#### src/Entity/CategoryFilter.php
```php
get('parent_id');
}
/**
* @param string $parentId
* @return $this
*/
public function setParentId($parentId)
{
$this->set('parent_id', $parentId);
return $this;
}
}
```
#### src/Entity/CategoryResult.php
```php
categoryManager = $categoryManager;
$this->authorizationChecker = $authorizationChecker;
$this->entityManager = $entityManager;
}
/**
* Enable category
* PUT /categories/{id}/enable
*
* @param Entities\Category $category
* @return Entities\Category
*/
public function enableCategory(Entities\Category $category)
{
$this->authorizationChecker->check(CategoryPermissions::ENABLE_CATEGORY);
$result = $this->categoryManager->enableCategory($category);
$this->entityManager->flush();
return $result;
}
/**
* Disable category
* PUT /categories/{id}/disable
*
* @param Entities\Category $category
* @return Entities\Category
*/
public function disableCategory(Entities\Category $category)
{
$this->authorizationChecker->check(CategoryPermissions::DISABLE_CATEGORY);
$result = $this->categoryManager->disableCategory($category);
$this->entityManager->flush();
return $result;
}
/**
* Update category
* PUT /categories/{id}
*
* @param Entities\Category $originalCategory
* @param Entities\Category $updatedCategory
* @return Entities\Category
*/
public function updateCategory(Entities\Category $originalCategory, Entities\Category $updatedCategory)
{
$this->authorizationChecker->check(CategoryPermissions::UPDATE_CATEGORY);
$result = $this->categoryManager->updateCategory($originalCategory, $updatedCategory);
$this->entityManager->flush();
return $result;
}
/**
* Delete category
* DELETE /categories/{id}
*
* @param Entities\Category $category
* @return null
*/
public function deleteCategory(Entities\Category $category)
{
$this->authorizationChecker->check(CategoryPermissions::DELETE_CATEGORY);
$this->categoryManager->deleteCategory($category);
$this->entityManager->flush();
return null;
}
/**
* Standard SQL-style Result filtering
* GET /categories
*
* @param Entities\CategoryFilter $categoryFilter
* @return null
*/
public function getCategories(Entities\CategoryFilter $categoryFilter)
{
$this->authorizationChecker->check(CategoryPermissions::GET_CATEGORIES);
return $this->categoryManager->getCategories($categoryFilter);
}
/**
* Create category
* POST /categories
*
* @param Entities\Category $category
* @return Result|Entities\Category[]
*/
public function createCategory(Entities\Category $category)
{
$this->authorizationChecker->check(CategoryPermissions::CREATE_CATEGORY);
$result = $this->categoryManager->createCategory($category);
$this->entityManager->flush();
return $result;
}
}
```
`DependencyInjection/Configuration.php`
```php
root('vendor_category_api');
return $treeBuilder;
}
}
```
`DependencyInjection/VendorCategoryApiExtension.php`
```php
processConfiguration($configuration, $configs);
$loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.xml');
}
}
```
`Entity/Category.php`
```php
id;
}
/**
* @return string|null
*/
public function getParentId()
{
return $this->parentId;
}
/**
* @param string $parentId
* @return $this
*/
public function setParentId($parentId)
{
$this->parentId = $parentId;
return $this;
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* @param string $name
* @return $this
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* @return string|null
*/
public function getStatus()
{
return $this->status;
}
/**
* @param string $status
* @return $this
*/
public function setStatus($status)
{
$this->status = $status;
return $this;
}
}
```
`Entity/CategoryFilter.php`
```php
parentId;
}
/**
* @param string $parentId
* @return $this
*/
public function setParentId($parentId)
{
$this->parentId = $parentId;
return $this;
}
}
```
`Normalizer/CategoryNormalizer.php`
```php
setParentId($data['parent_id']);
}
if (isset($data['name'])) {
$entity->setName($data['name']);
}
if (isset($data['status'])) {
$entity->setStatus($data['status']);
}
return $entity;
}
/**
* @param Category $entity
*
* @return array
*/
public function mapFromEntity($entity)
{
return [
'id' => $entity->getId(),
'parent_id' => $entity->getParentId(),
'name' => $entity->getName(),
'status' => $entity->getStatus(),
];
}
}
```
`Normalizer/CategoryFilterNormalizer.php`
```php
mapBaseKeys($data, $entity);
if (isset($data['parent_id'])) {
$entity->setParentId($data['parent_id']);
}
return $entity;
}
/**
* @param CategoryFilter $entity
*
* @return array
*/
public function mapFromEntity($entity)
{
$data = parent::mapFromEntity($entity);
return array_merge(
$data,
[
'parent_id' => $entity->getParentId(),
]
);
}
}
```
`Repository/CategoryRepository.php`
```php
categoryRepository = $categoryRepository;
$this->entityManager = $entityManager;
}
/**
* @param Entities\Category $category
* @return Entities\Category
*/
public function enableCategory(Entities\Category $category)
{
//TODO: generated_code
}
/**
* @param Entities\Category $category
* @return Entities\Category
*/
public function disableCategory(Entities\Category $category)
{
//TODO: generated_code
}
/**
* @param Entities\Category $category
* @param Entities\Category $category
* @return Entities\Category
*/
public function updateCategory(Entities\Category $originalCategory, Entities\Category $updatedCategory)
{
//TODO: generated_code
}
/**
* @param Entities\Category $category
* @return null
*/
public function deleteCategory(Entities\Category $category)
{
//TODO: generated_code
}
/**
* @param Entities\CategoryFilter $categoryFilter
* @return null
*/
public function getCategories(Entities\CategoryFilter $categoryFilter)
{
//TODO: generated_code
}
/**
* @param Entities\Category $category
* @return Result|Entities\Category[]
*/
public function createCategory(Entities\Category $category)
{
//TODO: generated_code
}
}
```
`Voter/CategoryScopeVoter.php`
```php
[
// TODO: generated_code
],
CategoryPermissions::CREATE_CATEGORY => [
// TODO: generated_code
],
CategoryPermissions::UPDATE_CATEGORY => [
// TODO: generated_code
],
CategoryPermissions::DELETE_CATEGORY => [
// TODO: generated_code
],
CategoryPermissions::ENABLE_CATEGORY => [
// TODO: generated_code
],
CategoryPermissions::DISABLE_CATEGORY => [
// TODO: generated_code
],
];
}
public function checkAccessRights(AccessedBy $accessedBy, $permission, $subject)
{
// TODO: generated_code
}
}
```
[ico-version]: https://img.shields.io/github/v/release/paysera/util-raml-code-generator?style=flat-square
[ico-travis]: https://img.shields.io/travis/paysera/util-raml-code-generator/master.svg?style=flat-square
[link-releases]: https://github.com/paysera/util-raml-code-generator/releases
[link-travis]: https://travis-ci.org/paysera/util-raml-code-generator