diff --git a/lib/codegen/codegen.dart b/lib/codegen/codegen.dart index ab1d0854f..eb9fc4458 100644 --- a/lib/codegen/codegen.dart +++ b/lib/codegen/codegen.dart @@ -10,6 +10,7 @@ import 'php/guzzle.dart'; import 'php/curl.dart'; import 'python/http_client.dart'; import 'python/requests.dart'; +import 'ruby/faraday.dart'; import 'rust/actix.dart'; import 'rust/curl_rust.dart'; import 'rust/reqwest.dart'; @@ -74,6 +75,8 @@ class Codegen { .getCode(rM, boundary: boundary ?? getNewUuid()); case CodegenLanguage.pythonRequests: return PythonRequestsCodeGen().getCode(rM, boundary: boundary); + case CodegenLanguage.rubyFaraday: + return RubyFaradayCodeGen().getCode(rM); case CodegenLanguage.rustActix: return RustActixCodeGen().getCode(rM, boundary: boundary); case CodegenLanguage.rustCurl: diff --git a/lib/codegen/ruby/faraday.dart b/lib/codegen/ruby/faraday.dart new file mode 100644 index 000000000..852f0dfb4 --- /dev/null +++ b/lib/codegen/ruby/faraday.dart @@ -0,0 +1,178 @@ +import 'package:apidash/consts.dart'; +import 'package:jinja/jinja.dart' as jj; +import 'package:apidash/utils/utils.dart' show getValidRequestUri; +import 'package:apidash/utils/http_utils.dart' show stripUriParams; + +import 'package:apidash/models/models.dart' show RequestModel; + +// Note that delete is a special case in Faraday as API Dash supports request +// body inside delete reqest, but Faraday does not. Hence we need to manually +// setup request body for delete request and add that to request. +// +// Refer https://lostisland.github.io/faraday/#/getting-started/quick-start?id=get-head-delete-trace +class RubyFaradayCodeGen { + final String kStringFaradayRequireStatement = """ +require 'uri' +require 'faraday' +"""; + + final String kStringFaradayMultipartRequireStatement = ''' +require 'faraday/multipart' +'''; + + final String kTemplateRequestUrl = """ + +REQUEST_URL = URI("{{ url }}") + + +"""; + + final String kTemplateBody = """ +PAYLOAD = < Faraday::Multipart::ParamPart.new("{{ param.value }}", "text/plain"), +{% elif param.type == "file" %} "{{ param.name }}" => Faraday::Multipart::FilePart.new("{{ param.value }}", "application/octet-stream"),{% endif %}{% endfor %} +} + + +"""; + + final String kTemplateFormParamsWithoutFile = """ +PAYLOAD = URI.encode_www_form({\n{% for param in params %} "{{ param.name }}" => "{{ param.value }}",\n{% endfor %}})\n\n +"""; + + final String kTemplateConnection = """ +conn = Faraday.new do |faraday| + faraday.adapter Faraday.default_adapter{% if hasFile %}\n faraday.request :multipart{% endif %} +end + + +"""; + + final String kTemplateRequestStart = """ +response = conn.{{ method|lower }}(REQUEST_URL{% if doesMethodAcceptBody and containsBody %}, PAYLOAD{% endif %}) do |req| + +"""; + + final String kTemplateRequestParams = """ + req.params = { +{% for key, val in params %} "{{ key }}" => "{{ val }}",\n{% endfor %} } + +"""; + + final String kTemplateRequestHeaders = """ + req.headers = { +{% for key, val in headers %} "{{ key }}" => "{{ val }}",\n{% endfor %} } + +"""; + + final String kStringDeleteRequestBody = """ + req.body = PAYLOAD +"""; + + final String kStringRequestEnd = """ +end + +"""; + + final String kStringResponse = """ +puts "Status Code: #{response.status}" +puts "Response Body: #{response.body}" +"""; + + String? getCode( + RequestModel requestModel, + ) { + try { + String result = ""; + + var rec = getValidRequestUri( + requestModel.url, + requestModel.enabledRequestParams, + ); + + Uri? uri = rec.$1; + + if (uri == null) { + return ""; + } + + var url = stripUriParams(uri); + + result += kStringFaradayRequireStatement; + if (requestModel.hasFormDataContentType && requestModel.hasFileInFormData) { + result += kStringFaradayMultipartRequireStatement; + } + + var templateRequestUrl = jj.Template(kTemplateRequestUrl); + result += templateRequestUrl.render({"url": url}); + + if (requestModel.hasFormData) { + jj.Template payload; + if (requestModel.hasFileInFormData) { + payload = jj.Template(kTemplateFormParamsWithFile); + } else { + payload = jj.Template(kTemplateFormParamsWithoutFile); + } + result += payload.render({"params": requestModel.formDataMapList}); + } else if (requestModel.hasJsonData || requestModel.hasTextData) { + var templateBody = jj.Template(kTemplateBody); + result += templateBody.render({ + "body": requestModel.requestBody, + }); + } + + // crreating faraday connection for request + var templateConnection = jj.Template(kTemplateConnection); + result += templateConnection.render({ + "hasFile": requestModel.hasFormDataContentType && requestModel.hasFileInFormData // + }); + + // start of the request sending + var templateRequestStart = jj.Template(kTemplateRequestStart); + result += templateRequestStart.render({ + "method": requestModel.method.name, + "doesMethodAcceptBody": + kMethodsWithBody.contains(requestModel.method) && requestModel.method != HTTPVerb.delete, // + "containsBody": requestModel.hasBody, + }); + + var headers = requestModel.enabledHeadersMap; + if (requestModel.hasBody && !requestModel.hasContentTypeHeader) { + if (requestModel.hasJsonData || requestModel.hasTextData) { + headers["Content-Type"] = requestModel.requestBodyContentType.header; + } + } + + if (headers.isNotEmpty) { + var templateRequestHeaders = jj.Template(kTemplateRequestHeaders); + result += templateRequestHeaders.render({"headers": headers}); + } + + if (uri.hasQuery) { + var params = uri.queryParameters; + if (params.isNotEmpty) { + var templateRequestParams = jj.Template(kTemplateRequestParams); + result += templateRequestParams.render({"params": params}); + } + } + + if (requestModel.hasBody && requestModel.method == HTTPVerb.delete) { + result += kStringDeleteRequestBody; + } + + result += kStringRequestEnd; + result += kStringResponse; + return result; + } catch (e) { + return null; + } + } +} diff --git a/lib/consts.dart b/lib/consts.dart index a7607dfcf..4f7bb60ef 100644 --- a/lib/consts.dart +++ b/lib/consts.dart @@ -283,6 +283,7 @@ enum CodegenLanguage { kotlinOkHttp("Kotlin (okhttp3)", "java", "kt"), pythonRequests("Python (requests)", "python", "py"), pythonHttpClient("Python (http.client)", "python", "py"), + rubyFaraday("Ruby (Faraday)", "ruby", "rb"), rustActix("Rust (Actix Client)", "rust", "rs"), rustReqwest("Rust (reqwest)", "rust", "rs"), rustCurl("Rust (curl-rust)", "rust", "rs"), diff --git a/test/codegen/ruby_faraday_codegen_test.dart b/test/codegen/ruby_faraday_codegen_test.dart new file mode 100644 index 000000000..c3d8fe521 --- /dev/null +++ b/test/codegen/ruby_faraday_codegen_test.dart @@ -0,0 +1,718 @@ +import 'package:apidash/codegen/codegen.dart'; +import 'package:apidash/consts.dart'; +import 'package:test/test.dart'; +import '../request_models.dart'; + +void main() { + final codeGen = Codegen(); + + group('GET Request', () { + test('GET 1', () { + const expectedCode = r"""require 'uri' +require 'faraday' + +REQUEST_URL = URI("https://api.apidash.dev") + +conn = Faraday.new do |faraday| + faraday.adapter Faraday.default_adapter +end + +response = conn.get(REQUEST_URL) do |req| +end + +puts "Status Code: #{response.status}" +puts "Response Body: #{response.body}" +"""; + expect(codeGen.getCode(CodegenLanguage.rubyFaraday, requestModelGet1, "https"), expectedCode); + }); + + test('GET 2', () { + const expectedCode = r"""require 'uri' +require 'faraday' + +REQUEST_URL = URI("https://api.apidash.dev/country/data") + +conn = Faraday.new do |faraday| + faraday.adapter Faraday.default_adapter +end + +response = conn.get(REQUEST_URL) do |req| + req.params = { + "code" => "US", + } +end + +puts "Status Code: #{response.status}" +puts "Response Body: #{response.body}" +"""; + expect(codeGen.getCode(CodegenLanguage.rubyFaraday, requestModelGet2, "https"), expectedCode); + }); + + test('GET 3', () { + const expectedCode = r"""require 'uri' +require 'faraday' + +REQUEST_URL = URI("https://api.apidash.dev/country/data") + +conn = Faraday.new do |faraday| + faraday.adapter Faraday.default_adapter +end + +response = conn.get(REQUEST_URL) do |req| + req.params = { + "code" => "IND", + } +end + +puts "Status Code: #{response.status}" +puts "Response Body: #{response.body}" +"""; + expect(codeGen.getCode(CodegenLanguage.rubyFaraday, requestModelGet3, "https"), expectedCode); + }); + + test('GET 4', () { + const expectedCode = r"""require 'uri' +require 'faraday' + +REQUEST_URL = URI("https://api.apidash.dev/humanize/social") + +conn = Faraday.new do |faraday| + faraday.adapter Faraday.default_adapter +end + +response = conn.get(REQUEST_URL) do |req| + req.params = { + "num" => "8700000", + "digits" => "3", + "system" => "SS", + "add_space" => "true", + "trailing_zeros" => "true", + } +end + +puts "Status Code: #{response.status}" +puts "Response Body: #{response.body}" +"""; + expect(codeGen.getCode(CodegenLanguage.rubyFaraday, requestModelGet4, "https"), expectedCode); + }); + + test('GET 5', () { + const expectedCode = r"""require 'uri' +require 'faraday' + +REQUEST_URL = URI("https://api.github.com/repos/foss42/apidash") + +conn = Faraday.new do |faraday| + faraday.adapter Faraday.default_adapter +end + +response = conn.get(REQUEST_URL) do |req| + req.headers = { + "User-Agent" => "Test Agent", + } +end + +puts "Status Code: #{response.status}" +puts "Response Body: #{response.body}" +"""; + expect(codeGen.getCode(CodegenLanguage.rubyFaraday, requestModelGet5, "https"), expectedCode); + }); + + test('GET 6', () { + const expectedCode = r"""require 'uri' +require 'faraday' + +REQUEST_URL = URI("https://api.github.com/repos/foss42/apidash") + +conn = Faraday.new do |faraday| + faraday.adapter Faraday.default_adapter +end + +response = conn.get(REQUEST_URL) do |req| + req.headers = { + "User-Agent" => "Test Agent", + } + req.params = { + "raw" => "true", + } +end + +puts "Status Code: #{response.status}" +puts "Response Body: #{response.body}" +"""; + expect(codeGen.getCode(CodegenLanguage.rubyFaraday, requestModelGet6, "https"), expectedCode); + }); + + test('GET 7', () { + const expectedCode = r"""require 'uri' +require 'faraday' + +REQUEST_URL = URI("https://api.apidash.dev") + +conn = Faraday.new do |faraday| + faraday.adapter Faraday.default_adapter +end + +response = conn.get(REQUEST_URL) do |req| +end + +puts "Status Code: #{response.status}" +puts "Response Body: #{response.body}" +"""; + expect(codeGen.getCode(CodegenLanguage.rubyFaraday, requestModelGet7, "https"), expectedCode); + }); + + test('GET 8', () { + const expectedCode = r"""require 'uri' +require 'faraday' + +REQUEST_URL = URI("https://api.github.com/repos/foss42/apidash") + +conn = Faraday.new do |faraday| + faraday.adapter Faraday.default_adapter +end + +response = conn.get(REQUEST_URL) do |req| + req.headers = { + "User-Agent" => "Test Agent", + } + req.params = { + "raw" => "true", + } +end + +puts "Status Code: #{response.status}" +puts "Response Body: #{response.body}" +"""; + expect(codeGen.getCode(CodegenLanguage.rubyFaraday, requestModelGet8, "https"), expectedCode); + }); + + test('GET 9', () { + const expectedCode = r"""require 'uri' +require 'faraday' + +REQUEST_URL = URI("https://api.apidash.dev/humanize/social") + +conn = Faraday.new do |faraday| + faraday.adapter Faraday.default_adapter +end + +response = conn.get(REQUEST_URL) do |req| + req.params = { + "num" => "8700000", + "add_space" => "true", + } +end + +puts "Status Code: #{response.status}" +puts "Response Body: #{response.body}" +"""; + expect(codeGen.getCode(CodegenLanguage.rubyFaraday, requestModelGet9, "https"), expectedCode); + }); + + test('GET 10', () { + const expectedCode = r"""require 'uri' +require 'faraday' + +REQUEST_URL = URI("https://api.apidash.dev/humanize/social") + +conn = Faraday.new do |faraday| + faraday.adapter Faraday.default_adapter +end + +response = conn.get(REQUEST_URL) do |req| + req.headers = { + "User-Agent" => "Test Agent", + } +end + +puts "Status Code: #{response.status}" +puts "Response Body: #{response.body}" +"""; + expect( + codeGen.getCode( + CodegenLanguage.rubyFaraday, + requestModelGet10, + "https", + ), + expectedCode); + }); + + test('GET 11', () { + const expectedCode = r"""require 'uri' +require 'faraday' + +REQUEST_URL = URI("https://api.apidash.dev/humanize/social") + +conn = Faraday.new do |faraday| + faraday.adapter Faraday.default_adapter +end + +response = conn.get(REQUEST_URL) do |req| + req.headers = { + "User-Agent" => "Test Agent", + } + req.params = { + "num" => "8700000", + "digits" => "3", + } +end + +puts "Status Code: #{response.status}" +puts "Response Body: #{response.body}" +"""; + expect(codeGen.getCode(CodegenLanguage.rubyFaraday, requestModelGet11, "https"), expectedCode); + }); + + test('GET 12', () { + const expectedCode = r"""require 'uri' +require 'faraday' + +REQUEST_URL = URI("https://api.apidash.dev/humanize/social") + +conn = Faraday.new do |faraday| + faraday.adapter Faraday.default_adapter +end + +response = conn.get(REQUEST_URL) do |req| +end + +puts "Status Code: #{response.status}" +puts "Response Body: #{response.body}" +"""; + expect(codeGen.getCode(CodegenLanguage.rubyFaraday, requestModelGet12, "https"), expectedCode); + }); + }); + + group('HEAD Request', () { + test('HEAD 1', () { + const expectedCode = r"""require 'uri' +require 'faraday' + +REQUEST_URL = URI("https://api.apidash.dev") + +conn = Faraday.new do |faraday| + faraday.adapter Faraday.default_adapter +end + +response = conn.head(REQUEST_URL) do |req| +end + +puts "Status Code: #{response.status}" +puts "Response Body: #{response.body}" +"""; + expect(codeGen.getCode(CodegenLanguage.rubyFaraday, requestModelHead1, "https"), expectedCode); + }); + + test('HEAD 2', () { + const expectedCode = r"""require 'uri' +require 'faraday' + +REQUEST_URL = URI("http://api.apidash.dev") + +conn = Faraday.new do |faraday| + faraday.adapter Faraday.default_adapter +end + +response = conn.head(REQUEST_URL) do |req| +end + +puts "Status Code: #{response.status}" +puts "Response Body: #{response.body}" +"""; + expect(codeGen.getCode(CodegenLanguage.rubyFaraday, requestModelHead2, "http"), expectedCode); + }); + }); + + group('POST Request', () { + test('POST 1', () { + const expectedCode = r"""require 'uri' +require 'faraday' + +REQUEST_URL = URI("https://api.apidash.dev/case/lower") + +PAYLOAD = < "text/plain", + } +end + +puts "Status Code: #{response.status}" +puts "Response Body: #{response.body}" +"""; + expect( + codeGen.getCode(CodegenLanguage.rubyFaraday, requestModelPost1, "https"), + expectedCode); + }); + + test('POST 2', () { + const expectedCode = r"""require 'uri' +require 'faraday' + +REQUEST_URL = URI("https://api.apidash.dev/case/lower") + +PAYLOAD = < "application/json", + } +end + +puts "Status Code: #{response.status}" +puts "Response Body: #{response.body}" +"""; + expect( + codeGen.getCode(CodegenLanguage.rubyFaraday, requestModelPost2, "https"), + expectedCode); + }); + + test('POST 3', () { + const expectedCode = r"""require 'uri' +require 'faraday' + +REQUEST_URL = URI("https://api.apidash.dev/case/lower") + +PAYLOAD = < "Test Agent", + "Content-Type" => "application/json", + } +end + +puts "Status Code: #{response.status}" +puts "Response Body: #{response.body}" +"""; + expect( + codeGen.getCode(CodegenLanguage.rubyFaraday, requestModelPost3, "https"), + expectedCode); + }); + + test('POST 4', () { + const expectedCode = r"""require 'uri' +require 'faraday' + +REQUEST_URL = URI("https://api.apidash.dev/io/form") + +PAYLOAD = URI.encode_www_form({ + "text" => "API", + "sep" => "|", + "times" => "3", +}) + +conn = Faraday.new do |faraday| + faraday.adapter Faraday.default_adapter +end + +response = conn.post(REQUEST_URL, PAYLOAD) do |req| +end + +puts "Status Code: #{response.status}" +puts "Response Body: #{response.body}" +"""; + expect( + codeGen.getCode( + CodegenLanguage.rubyFaraday, + requestModelPost4, + "https", + ), + expectedCode); + }); + test('POST 5', () { + const expectedCode = r"""require 'uri' +require 'faraday' + +REQUEST_URL = URI("https://api.apidash.dev/io/form") + +PAYLOAD = URI.encode_www_form({ + "text" => "API", + "sep" => "|", + "times" => "3", +}) + +conn = Faraday.new do |faraday| + faraday.adapter Faraday.default_adapter +end + +response = conn.post(REQUEST_URL, PAYLOAD) do |req| + req.headers = { + "User-Agent" => "Test Agent", + } +end + +puts "Status Code: #{response.status}" +puts "Response Body: #{response.body}" +"""; + expect(codeGen.getCode(CodegenLanguage.rubyFaraday, requestModelPost5, "https"), expectedCode); + }); + test('POST 6', () { + const expectedCode = r"""require 'uri' +require 'faraday' +require 'faraday/multipart' + +REQUEST_URL = URI("https://api.apidash.dev/io/img") + +PAYLOAD = { + "token" => Faraday::Multipart::ParamPart.new("xyz", "text/plain"), + "imfile" => Faraday::Multipart::FilePart.new("/Documents/up/1.png", "application/octet-stream"), +} + +conn = Faraday.new do |faraday| + faraday.adapter Faraday.default_adapter + faraday.request :multipart +end + +response = conn.post(REQUEST_URL, PAYLOAD) do |req| +end + +puts "Status Code: #{response.status}" +puts "Response Body: #{response.body}" +"""; + expect( + codeGen.getCode(CodegenLanguage.rubyFaraday, requestModelPost6, "https"), + expectedCode); + }); + test('POST 7', () { + const expectedCode = r"""require 'uri' +require 'faraday' +require 'faraday/multipart' + +REQUEST_URL = URI("https://api.apidash.dev/io/img") + +PAYLOAD = { + "token" => Faraday::Multipart::ParamPart.new("xyz", "text/plain"), + "imfile" => Faraday::Multipart::FilePart.new("/Documents/up/1.png", "application/octet-stream"), +} + +conn = Faraday.new do |faraday| + faraday.adapter Faraday.default_adapter + faraday.request :multipart +end + +response = conn.post(REQUEST_URL, PAYLOAD) do |req| +end + +puts "Status Code: #{response.status}" +puts "Response Body: #{response.body}" +"""; + expect( + codeGen.getCode(CodegenLanguage.rubyFaraday, requestModelPost7, "https"), + expectedCode); + }); + test('POST 8', () { + const expectedCode = r"""require 'uri' +require 'faraday' + +REQUEST_URL = URI("https://api.apidash.dev/io/form") + +PAYLOAD = URI.encode_www_form({ + "text" => "API", + "sep" => "|", + "times" => "3", +}) + +conn = Faraday.new do |faraday| + faraday.adapter Faraday.default_adapter +end + +response = conn.post(REQUEST_URL, PAYLOAD) do |req| + req.params = { + "size" => "2", + "len" => "3", + } +end + +puts "Status Code: #{response.status}" +puts "Response Body: #{response.body}" +"""; + expect(codeGen.getCode(CodegenLanguage.rubyFaraday, requestModelPost8, "https"), expectedCode); + }); + test('POST 9', () { + const expectedCode = r"""require 'uri' +require 'faraday' +require 'faraday/multipart' + +REQUEST_URL = URI("https://api.apidash.dev/io/img") + +PAYLOAD = { + "token" => Faraday::Multipart::ParamPart.new("xyz", "text/plain"), + "imfile" => Faraday::Multipart::FilePart.new("/Documents/up/1.png", "application/octet-stream"), +} + +conn = Faraday.new do |faraday| + faraday.adapter Faraday.default_adapter + faraday.request :multipart +end + +response = conn.post(REQUEST_URL, PAYLOAD) do |req| + req.headers = { + "User-Agent" => "Test Agent", + "Keep-Alive" => "true", + } + req.params = { + "size" => "2", + "len" => "3", + } +end + +puts "Status Code: #{response.status}" +puts "Response Body: #{response.body}" +"""; + expect( + codeGen.getCode(CodegenLanguage.rubyFaraday, requestModelPost9, "https"), + expectedCode); + }); + }); + + group('PUT Request', () { + test('PUT 1', () { + const expectedCode = r"""require 'uri' +require 'faraday' + +REQUEST_URL = URI("https://reqres.in/api/users/2") + +PAYLOAD = < "application/json", + } +end + +puts "Status Code: #{response.status}" +puts "Response Body: #{response.body}" +"""; + expect( + codeGen.getCode(CodegenLanguage.rubyFaraday, requestModelPut1, "https"), + expectedCode); + }); + }); + + group('PATCH Request', () { + test('PATCH 1', () { + const expectedCode = r"""require 'uri' +require 'faraday' + +REQUEST_URL = URI("https://reqres.in/api/users/2") + +PAYLOAD = < "application/json", + } +end + +puts "Status Code: #{response.status}" +puts "Response Body: #{response.body}" +"""; + expect( + codeGen.getCode(CodegenLanguage.rubyFaraday, requestModelPatch1, "https"), + expectedCode); + }); + }); + + group('DELETE Request', () { + test('DELETE 1', () { + const expectedCode = r"""require 'uri' +require 'faraday' + +REQUEST_URL = URI("https://reqres.in/api/users/2") + +conn = Faraday.new do |faraday| + faraday.adapter Faraday.default_adapter +end + +response = conn.delete(REQUEST_URL) do |req| +end + +puts "Status Code: #{response.status}" +puts "Response Body: #{response.body}" +"""; + expect(codeGen.getCode(CodegenLanguage.rubyFaraday, requestModelDelete1, "https"), expectedCode); + }); + + test('DELETE 2', () { + const expectedCode = r"""require 'uri' +require 'faraday' + +REQUEST_URL = URI("https://reqres.in/api/users/2") + +PAYLOAD = < "application/json", + } + req.body = PAYLOAD +end + +puts "Status Code: #{response.status}" +puts "Response Body: #{response.body}" +"""; + expect( + codeGen.getCode(CodegenLanguage.rubyFaraday, requestModelDelete2, "https"), + expectedCode); + }); + }); +}