yobi-http 0.12.3 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2f6c3d121288f8be81462aaddc7e38a9fb26845fd4751d839458979e3d935e4d
4
- data.tar.gz: 8302b717557971b6c1cf321ce1564ee2d466b3ae539615476563397f4564c92f
3
+ metadata.gz: 3558906f242a7670158833610ad7b062bf25e308e4985804241bdba30a9d6968
4
+ data.tar.gz: d96e600ccdf03ebcddb0c9ce14a3a2c141fb1a1ba2ffe04ee7cdb0a1a5383c7d
5
5
  SHA512:
6
- metadata.gz: bbfd154631d0368c482945ff2a72519a02612c18757a65c24a0ca72881ba4e05d7b8737971af0efc3199eda367fa033b5d0839330e8876d109b65fab43c2059c
7
- data.tar.gz: 06cc2d5e0cecf1a536975a9edbd6ab3f1c014dd9741df6f9420418ae9832ad41be55f802c87a37088ce05d4210232a9791950e257ac349460c9e346cce21faa1
6
+ metadata.gz: 68ec04f8b731ebc0e9f508ed332d6414292963d1dbbd97aaba96a360e930ce3d1421812d11c1031305126633db9ad012fb2f4efa6e170aba4e09e3eeef37cf3e
7
+ data.tar.gz: 61c69a1bfdc761df2c791396fb84a3ce5183bf24b71ffb4073fbebdbd1e86b3e5e6986fbeeb977a7c2225624d72d083270e585e0afe186030066b3dab05f0e6c
data/Examples.md CHANGED
@@ -23,6 +23,13 @@ yobi --debug POST https://httpbin.org/post name=Yobi type="HTTP client"
23
23
  yobi --debug POST https://httpbin.org/post name=Yobi type="HTTP client" --form
24
24
  ```
25
25
 
26
+ ## HTTP request with complex JSON body
27
+
28
+ ```bash
29
+ yobi --debug POST https://httpbin.org/post name=Yobi meta:='{"category": "HTTP client", "type": "rubygem", "year": 2026 }' released:=true tags:'["ruby", "http", "gem"]'
30
+ ```
31
+
32
+
26
33
  ## HTTP request with query parameters
27
34
 
28
35
  ```bash
data/README.md CHANGED
@@ -1,3 +1,7 @@
1
+ [![Gem Version](https://badge.fury.io/rb/yobi-http.svg?icon=si%3Arubygems)](https://badge.fury.io/rb/yobi-http)
2
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
3
+ [![Maintainability](https://qlty.sh/gh/dvinciguerra/projects/yobi-http/maintainability.svg)](https://qlty.sh/gh/dvinciguerra/projects/yobi-http)
4
+
1
5
  # Yobi(呼び) Http Client
2
6
 
3
7
  Yobi is a Ruby gem that provides a simple and efficient way to make HTTP requests. It is designed to be easy to use
data/exe/yobi CHANGED
@@ -16,7 +16,7 @@ require "yobi"
16
16
  # parsing arguments
17
17
  @options = {
18
18
  print: "HB", auth: nil, auth_type: "basic", verbose: false, raw: false, offline: false, follow: false, debug: false,
19
- timeout: nil, download: false
19
+ timeout: nil, download: false, output: nil, content_type: :json
20
20
  }
21
21
 
22
22
  parser = OptionParser.new do |opts|
@@ -28,7 +28,7 @@ parser = OptionParser.new do |opts|
28
28
 
29
29
  Examples:
30
30
  yobi https://api.example.com/users
31
- yobi POST https://api.example.com/users name=John age=30
31
+ yobi POST https://api.example.com/products name="New Product" price:=19.99 enabled:=true tags:=@tags.json
32
32
  yobi GET :8080/api/items Authorization:"Bearer $TOKEN"
33
33
  yobi -A basic -a user:pass https://api.example.com/secure-data
34
34
  yobi --raw --print B POST https://httpbin.org/anything | jq '.headers["User-Agent"]'
@@ -53,6 +53,27 @@ parser = OptionParser.new do |opts|
53
53
  @options[:raw] = true
54
54
  end
55
55
 
56
+ opts.on("--form", "Send data as application/x-www-form-urlencoded instead of JSON") do
57
+ @options[:content_type] = :form
58
+ end
59
+
60
+ opts.on("--multipart", "Send data as multipart/form-data instead of JSON") do
61
+ @options[:content_type] = :multipart
62
+ end
63
+
64
+ opts.on("--xml", "Send data as application/xml instead of JSON") do
65
+ @options[:content_type] = :xml
66
+ end
67
+
68
+ opts.on("--binary", "Send data as application/octet-stream instead of JSON") do
69
+ @options[:content_type] = :binary
70
+ end
71
+
72
+ opts.on("--json", "Send data as application/json (default)") do
73
+ # This option is implicit since JSON is the default, but we can set it explicitly if needed
74
+ @options[:content_type] = :json
75
+ end
76
+
56
77
  opts.on("-h", "--help", "Print this help message") do
57
78
  puts opts
58
79
  exit
@@ -101,13 +122,14 @@ method = Yobi.args.http_method?(ARGV[0]) ? ARGV.shift : "GET"
101
122
  # resolve the URL
102
123
  url = Yobi.args.url(ARGV.shift)
103
124
 
104
- data = Yobi.args.parse_data(ARGV)
105
- headers = Yobi.args.parse_headers(ARGV)
125
+ data = Yobi.args.parse_data(ARGV, @options)
126
+ query = Yobi.args.parse_query(ARGV, @options)
127
+ headers = Yobi.args.parse_headers(ARGV, @options)
106
128
 
107
129
  # prepare authentication header if auth is provided
108
130
  Yobi.args.auth_header(headers, @options) if @options[:auth]
109
131
 
110
- Yobi::Http.request(method, url, data: data, headers: headers, options: @options) do |http, request|
132
+ Yobi::Http.request(method, url, data: data, headers: headers, query: query, options: @options) do |http, request|
111
133
  options = @options
112
134
 
113
135
  # follow redirects, offline, download or standard request mode
@@ -22,13 +22,47 @@ module Yobi
22
22
  end
23
23
  end
24
24
 
25
- def parse_data(args)
26
- args.select { |arg| arg.include?("=") }.map.to_h { |arg| arg.split("=", 2).map(&:strip) }
25
+ def parse_query(args, _options)
26
+ args.select { |arg| arg.match?(/::/) }.map.to_h { |arg| arg.split("::", 2).map(&:strip) }
27
27
  end
28
28
 
29
- def parse_headers(args)
30
- { "Content-Type" => "application/json", "User-Agent" => "#{Yobi.name}/#{Yobi::VERSION}" }
31
- .merge(args.select { |arg| arg.include?(":") }.map.to_h { |arg| arg.split(":", 2).map(&:strip) })
29
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
30
+ def parse_data(args, _options)
31
+ args.select { |arg| arg.match?("^.*(={1}|:=|=@|:=@).*$") }.map do |arg|
32
+ case arg
33
+ when /:=@/
34
+ arg.split(":=@", 2).map do |part|
35
+ part = String(part)&.strip
36
+ File.exist?(part) ? JSON.parse(File.read(part)) : part
37
+ end
38
+ when /:=/
39
+ arg.split(/:=/, 2).map.with_index do |part, index|
40
+ part = String(part)&.strip
41
+ index.zero? ? part : JSON.parse(part)
42
+ rescue JSON::ParserError => e
43
+ warn "Error #{e}: #{part}"
44
+ exit 1
45
+ end
46
+ when /=@/
47
+ arg.split(/=@/, 2).map do |part|
48
+ part = String(part)&.strip
49
+ File.exist?(part) ? File.read(part)&.strip : part
50
+ end
51
+ else
52
+ arg.split("=", 2).map(&:strip)
53
+ end
54
+ end.compact.to_h
55
+ end
56
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
57
+
58
+ def parse_headers(args, options)
59
+ content_type = content_type_for options[:content_type]
60
+
61
+ { "Content-Type" => content_type, "User-Agent" => "#{Yobi.name}/#{Yobi::VERSION}" }.merge(
62
+ args
63
+ .select { |arg| arg.match?(/:{1}/) && !arg.match?(/:=/) && !arg.match?(/::/) }
64
+ .map.to_h { |arg| arg.split(/:{1}/, 2).map(&:strip) }
65
+ )
32
66
  end
33
67
 
34
68
  def auth_header(headers, options)
@@ -46,6 +80,25 @@ module Yobi
46
80
  exit 1
47
81
  end
48
82
  end
83
+
84
+ private
85
+
86
+ def content_type_for(value)
87
+ case value
88
+ when :form
89
+ "application/x-www-form-urlencoded"
90
+ when :multipart
91
+ "multipart/form-data"
92
+ when :xml
93
+ "application/xml"
94
+ when :binary
95
+ "application/octet-stream"
96
+ when :json, nil
97
+ "application/json"
98
+ else
99
+ "application/json"
100
+ end
101
+ end
49
102
  end
50
103
  end
51
104
  end
data/lib/yobi/http.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "json"
3
4
  require "net/http"
4
5
 
5
6
  module Yobi
@@ -7,10 +8,22 @@ module Yobi
7
8
  module Http
8
9
  METHODS = %w[GET POST PUT DELETE PATCH HEAD OPTIONS].freeze
9
10
 
11
+ CONTENT_TYPES = {
12
+ text: "text/plain",
13
+ html: "text/html",
14
+ form: "application/x-www-form-urlencoded",
15
+ multipart: "multipart/form-data",
16
+ xml: "application/xml",
17
+ binary: "application/octet-stream",
18
+ json: "application/json"
19
+ }.freeze
20
+
10
21
  class << self
11
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
12
- def request(method, url, data: {}, headers: {}, options: {})
22
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/ParameterLists
23
+ def request(method, url, data: {}, query: {}, headers: {}, options: {})
13
24
  @uri = URI(url)
25
+ @uri.query = URI.encode_www_form(**query) unless query.empty?
26
+
14
27
  @options = options
15
28
  @method = method.capitalize
16
29
 
@@ -33,7 +46,7 @@ module Yobi
33
46
  exit 1
34
47
  end
35
48
  end
36
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
49
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/ParameterLists
37
50
 
38
51
  # rubocop:disable Metrics/ParameterLists
39
52
  def follow_redirects(response, url, method, data, headers, options)
@@ -50,7 +63,7 @@ module Yobi
50
63
  end
51
64
  # rubocop:enable Metrics/ParameterLists
52
65
 
53
- # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
66
+ # rubocop:disable Metrics/AbcSize
54
67
  def offline_mode(_request, options)
55
68
  Net::HTTP.class_eval do
56
69
  def connect; end
@@ -59,7 +72,7 @@ module Yobi
59
72
  options[:verbose] = true
60
73
 
61
74
  Net::HTTPResponse.new("1.1", "200", "OK").tap do |response|
62
- response["Content-Type"] = "application/json"
75
+ response["Content-Type"] = CONTENT_TYPES.fetch(options[:content_type], CONTENT_TYPES[:json])
63
76
  response["Access-Control-Allow-Credentials"] = true
64
77
  response["Access-Control-Allow-Origin"] = "*"
65
78
  response["Connection"] = "close"
@@ -71,9 +84,9 @@ module Yobi
71
84
  response.instance_variable_set(:@read, true)
72
85
  end
73
86
  end
74
- # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
87
+ # rubocop:enable Metrics/AbcSize
75
88
 
76
- # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
89
+ # rubocop:disable Metrics/AbcSize
77
90
  def download(request, http, options)
78
91
  http.request(request) do |response|
79
92
  url = request.uri.to_s
@@ -93,7 +106,7 @@ module Yobi
93
106
 
94
107
  exit 0
95
108
  end
96
- # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
109
+ # rubocop:enable Metrics/AbcSize
97
110
  end
98
111
  end
99
112
  end
data/lib/yobi/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Yobi
4
4
  # Yobi gem version
5
- VERSION = "0.12.3"
5
+ VERSION = "0.16.0"
6
6
  end
@@ -59,6 +59,53 @@ RSpec.describe Yobi::CLI::Arguments do
59
59
 
60
60
  expect(described_class.parse_data(args)).to eq({ "name" => "John", "age" => "30" })
61
61
  end
62
+
63
+ context "when given an json string" do
64
+ it "parses the json string into a hash" do
65
+ args = ['data:={ "name": "John", "age": 30, "admin": false, "friends": ["Jane", "Doe"] }']
66
+
67
+ expect(described_class.parse_data(args))
68
+ .to eq({ "data" => { "name" => "John", "age" => 30, "admin" => false, "friends" => %w[Jane Doe] } })
69
+ end
70
+
71
+ it "raises an error if the json string is invalid" do
72
+ args = ['data:={"name": "John", "age": 30, "city": "New York"'] # Missing closing brace
73
+
74
+ expect { described_class.parse_data(args) }.to raise_error(SystemExit)
75
+ end
76
+ end
77
+
78
+ context "when given a json file path" do
79
+ let(:json_content) do
80
+ {
81
+ "title" => "Sample JSON",
82
+ "description" => "This is a sample JSON file.",
83
+ "version" => 1.0,
84
+ "tags" => %w[sample json example],
85
+ "metadata" => {
86
+ "author" => "John Doe",
87
+ "created" => "2024-06-01T12:00:00Z",
88
+ "updated" => "2024-06-10T15:30:00Z"
89
+ },
90
+ "published" => true,
91
+ "more_info" => nil
92
+ }
93
+ end
94
+
95
+ before do
96
+ File.write("sample.json", json_content.to_json)
97
+ end
98
+
99
+ after do
100
+ File.delete("sample.json") if File.exist?("sample.json")
101
+ end
102
+
103
+ it "parses the json file content into a hash" do
104
+ args = ["data:=@sample.json"]
105
+
106
+ expect(described_class.parse_data(args)).to eq({ "data" => json_content })
107
+ end
108
+ end
62
109
  end
63
110
 
64
111
  describe ".parse_headers" do
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yobi-http
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.3
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Vinciguerra
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-02-25 00:00:00.000000000 Z
10
+ date: 2026-03-05 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: base64