Skip to content

Commit a75c28b

Browse files
committed
support Rust
1 parent 4979f41 commit a75c28b

File tree

6 files changed

+169
-29
lines changed

6 files changed

+169
-29
lines changed

src/components/client.ts

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { promisify } from 'util';
77
const writeFile = promisify(fs.writeFile);
88
import TOML from '@iarna/toml';
99
import { template } from 'lodash';
10-
import { normalizeMethodName } from './helper';
10+
import { normalizeMethodNameJavascript, normalizeMethodNameRust, extractParameterNames } from './helper';
1111

1212
const tsTemplate = template(
1313
`
@@ -213,10 +213,10 @@ export class <%= className %> {
213213
214214
<% openrpcDocument.methods.forEach((method) => { %>
215215
/**
216-
* <%= method.summary %>
216+
* <%= method.description %>
217217
*/
218218
// tslint:disable-next-line:max-line-length
219-
public <%= normalizeMethodName(method.name) %>: <%= methodTypings.getTypingNames("typescript", method).method %> = (...params) => {
219+
public <%= normalizeMethodNameJavascript(method.name) %>: <%= methodTypings.getTypingNames("typescript", method).method %> = (...params) => {
220220
return this.request("<%= method.name %>", params);
221221
}
222222
<% }); %>
@@ -225,21 +225,54 @@ export default <%= className %>;
225225
`,
226226
{
227227
imports: {
228-
normalizeMethodName,
228+
normalizeMethodNameJavascript,
229229
},
230230
}
231231
);
232232

233-
const rsTemplate = template(`
234-
#[macro_use]
235-
extern crate jsonrpc_client_core;
233+
const rsTemplate = template(
234+
`// @generated
235+
// Code generated by @open-rpc/generator DO NOT EDIT.
236+
237+
use jsonrpc_client_core::{
238+
Transport, RpcRequest, call_method
239+
};
236240
237241
<%= methodTypings.toString("rust", { includeSchemaTypings: true, includeMethodAliasTypings: false }) %>
238242
239-
jsonrpc_client!(pub struct <%= className %> {
240-
<%= methodTypings.toString("rust", { includeSchemaTypings: false, includeMethodAliasTypings: true }) %>
241-
});
242-
`);
243+
pub struct <%= className %><T: Transport> {
244+
transport: T,
245+
}
246+
247+
impl <T: Transport> <%= className %><T> {
248+
249+
pub fn new(transport: T) -> Self {
250+
Self { transport }
251+
}
252+
253+
<% openrpcDocument.methods.forEach((method) => { %>
254+
/**
255+
* <%= method.description %>
256+
*/
257+
pub fn <%= normalizeMethodNameRust(method.name) %>(&mut self, <%= methodTypings.getParamsTyping("rust", method) %>) -> RpcRequest<<%= methodTypings.getTypingNames("rust", method).result %>, T::Future> {
258+
let method = String::from(stringify!(<%= method.name %>));
259+
let params: HashMap<&str, serde_json::Value> = HashMap::from([
260+
<% extractParameterNames(methodTypings.getParamsTyping("rust", method)).forEach((paramName, index) => { %>
261+
("<%= paramName %>", serde_json::to_value(<%= paramName %>).unwrap()),
262+
<% }); %>
263+
]);
264+
call_method(&mut self.transport, method, params)
265+
}
266+
<% }); %>
267+
}
268+
`,
269+
{
270+
imports: {
271+
normalizeMethodNameRust,
272+
extractParameterNames,
273+
},
274+
}
275+
);
243276

244277
const hooks: IHooks = {
245278
afterCopyStatic: [

src/components/docs.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as path from 'path';
22
import { copy, ensureDir, remove } from 'fs-extra';
33
import { IHooks, IComponent } from './types';
4-
import { IDocsConfig, IDocsExtraConfig } from 'src/config';
4+
import { IDocsExtraConfig } from 'src/config';
55
import * as fs from 'fs';
66
import { promisify } from 'util';
77
import { template, startCase } from 'lodash';
@@ -198,7 +198,7 @@ const hooks: IHooks = {
198198
],
199199
},
200200
afterCompileTemplate: [
201-
async (dest, frm, component, openrpcDocument): Promise<void> => {
201+
async (dest, frm, component, _): Promise<void> => {
202202
const docsComponent = component as IComponent<IDocsExtraConfig>;
203203
if (!docsComponent.extraConfig) {
204204
return;

src/components/helper.test.ts

Lines changed: 87 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,104 @@
11
import { describe, it, expect } from '@jest/globals';
2-
import { normalizeMethodName } from './helper';
2+
import {
3+
normalizeMethodNameJavascript,
4+
normalizeMethodNameRust,
5+
extractParameterNames,
6+
} from './helper';
37

4-
describe('normalizeMethodName', () => {
8+
describe('Test normalizeMethodNameJavascript', () => {
59
it('returns the original name if it does not contain a dot', () => {
6-
expect(normalizeMethodName('hello')).toBe('hello');
7-
expect(normalizeMethodName('foo_bar')).toBe('foo_bar');
10+
expect(normalizeMethodNameJavascript('hello')).toBe('hello');
11+
expect(normalizeMethodNameJavascript('foo_bar')).toBe('foo_bar');
812
});
913

1014
it('splits and capitalizes correctly with dot', () => {
11-
expect(normalizeMethodName('foo.bar')).toBe('fooBar');
12-
expect(normalizeMethodName('foo.bar.baz')).toBe('fooBarBaz');
15+
expect(normalizeMethodNameJavascript('foo.bar')).toBe('fooBar');
16+
expect(normalizeMethodNameJavascript('foo.bar.baz')).toBe('fooBarBaz');
1317
});
1418

1519
it('capitalizes only subsequent parts', () => {
16-
expect(normalizeMethodName('one.two.three')).toBe('oneTwoThree');
20+
expect(normalizeMethodNameJavascript('one.two.three')).toBe('oneTwoThree');
1721
});
1822

1923
it('handles leading/trailing separators gracefully', () => {
20-
expect(normalizeMethodName('.foo.bar')).toBe('FooBar');
21-
expect(normalizeMethodName('foo.bar.')).toBe('fooBar');
22-
expect(normalizeMethodName('..foo..bar..')).toBe('FooBar');
24+
expect(normalizeMethodNameJavascript('.foo.bar')).toBe('FooBar');
25+
expect(normalizeMethodNameJavascript('foo.bar.')).toBe('fooBar');
26+
expect(normalizeMethodNameJavascript('..foo..bar..')).toBe('FooBar');
2327
});
2428

2529
it('handles empty string', () => {
26-
expect(normalizeMethodName('')).toBe('');
30+
expect(normalizeMethodNameJavascript('')).toBe('');
31+
});
32+
});
33+
34+
describe('Test normalizeMethodNameRust', () => {
35+
it('returns the original name if it does not contain a dot', () => {
36+
expect(normalizeMethodNameRust('hello')).toBe('hello');
37+
expect(normalizeMethodNameRust('foo_bar')).toBe('foo_bar');
38+
});
39+
40+
it('splits and capitalizes correctly with dot', () => {
41+
expect(normalizeMethodNameRust('foo.bar')).toBe('foo_bar');
42+
expect(normalizeMethodNameRust('foo.bar.baz')).toBe('foo_bar_baz');
43+
});
44+
45+
it('capitalizes only subsequent parts', () => {
46+
expect(normalizeMethodNameRust('one.two.three')).toBe('one_two_three');
47+
});
48+
49+
it('handles leading/trailing separators gracefully', () => {
50+
expect(normalizeMethodNameRust('.foo.bar')).toBe('foo_bar');
51+
expect(normalizeMethodNameRust('foo.bar.')).toBe('foo_bar');
52+
expect(normalizeMethodNameRust('..foo..bar..')).toBe('foo_bar');
53+
});
54+
55+
it('handles empty string', () => {
56+
expect(normalizeMethodNameRust('')).toBe('');
57+
});
58+
});
59+
60+
describe('Test extractParameterNames', () => {
61+
it('extracts parameter names from a string with types', () => {
62+
expect(extractParameterNames('number:int, address: string')).toEqual(['number', 'address']);
63+
expect(extractParameterNames('id:number, name:string, active:boolean')).toEqual([
64+
'id',
65+
'name',
66+
'active',
67+
]);
68+
});
69+
70+
it('handles single parameter', () => {
71+
expect(extractParameterNames('value:string')).toEqual(['value']);
72+
});
73+
74+
it('handles parameters without types', () => {
75+
expect(extractParameterNames('param1, param2, param3')).toEqual(['param1', 'param2', 'param3']);
76+
});
77+
78+
it('handles mixed parameters with and without types', () => {
79+
expect(extractParameterNames('id:number, name, active:boolean')).toEqual([
80+
'id',
81+
'name',
82+
'active',
83+
]);
84+
});
85+
86+
it('handles empty string', () => {
87+
expect(extractParameterNames('')).toEqual([]);
88+
});
89+
90+
it('handles string with only whitespace', () => {
91+
expect(extractParameterNames(' ')).toEqual([]);
92+
});
93+
94+
it('handles parameters with extra whitespace', () => {
95+
expect(extractParameterNames(' number : int , address : string ')).toEqual([
96+
'number',
97+
'address',
98+
]);
99+
});
100+
101+
it('filters out empty parameter names', () => {
102+
expect(extractParameterNames('valid:type, , another:type')).toEqual(['valid', 'another']);
27103
});
28104
});

src/components/helper.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
export function normalizeMethodName(name: string): string {
1+
// This function is used in the template files to normalize method names for JavaScript/TypeScript.
2+
// It removes dots and applies camelCase formatting.
3+
export function normalizeMethodNameJavascript(name: string): string {
24
// backward compatibility only change on dot inside in name.
35
if (!name.includes('.')) return name;
46

@@ -7,3 +9,35 @@ export function normalizeMethodName(name: string): string {
79
.map((part, index) => (index === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1)))
810
.join('');
911
}
12+
13+
// This function is used in the template files to normalize method names for Rust.
14+
// It replace dots with underscores and applies snake_case formatting.
15+
export function normalizeMethodNameRust(name: string): string {
16+
// backward compatibility only change on dot inside in name.
17+
if (!name.includes('.')) return name;
18+
19+
name = name.replace(/\./g, '_');
20+
name = name.toLowerCase();
21+
name = name.replace(/__+/g, '_'); // replace multiple underscores with a single underscore
22+
while (name.startsWith('_')) {
23+
name = name.slice(1); // remove leading underscore
24+
}
25+
while (name.endsWith('_')) {
26+
name = name.slice(0, -1); // remove trailing underscore
27+
}
28+
29+
return name;
30+
}
31+
32+
// Extract parameter names from a parameter string into an array
33+
export function extractParameterNames(paramStr: string): string[] {
34+
if (!paramStr || paramStr.trim() === '') {
35+
return [];
36+
}
37+
38+
return paramStr
39+
.split(',')
40+
.map((param) => param.trim())
41+
.map((param) => param.split(':')[0].trim())
42+
.filter((name) => name.length > 0);
43+
}

src/index.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { forEach } from 'lodash';
77
import { OpenRPCDocumentDereferencingError } from '@open-rpc/schema-utils-js';
88
import { OpenrpcDocument as OpenRPC } from '@open-rpc/meta-schema';
99
import { describe, it, expect, beforeAll, afterAll } from '@jest/globals';
10-
import { isMapIterator } from 'util/types';
1110

1211
const stat = promisify(fs.stat);
1312
const rmdir = promisify(fs.rmdir);

src/index.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,15 @@ import { promisify } from 'util';
55
import { startCase } from 'lodash';
66
import { OpenrpcDocument as OpenRPC } from '@open-rpc/meta-schema';
77
import { parseOpenRPCDocument } from '@open-rpc/schema-utils-js';
8-
import { IDocsConfig, TComponentConfig } from './config';
8+
import { TComponentConfig } from './config';
99
import Typings from '@open-rpc/typings';
1010

1111
import {
1212
defaultClientComponent,
1313
defaultDocComponent,
1414
defaultServerComponent,
1515
IComponentModule,
16-
IHooks,
1716
FHook,
18-
getDefaultComponentTemplatePath,
1917
IComponent,
2018
} from './components';
2119
export * as components from './components';

0 commit comments

Comments
 (0)