Skip to content

Commit b643d82

Browse files
authored
Add Bruno collection (.bru) analyzer (#1684)
Bruno is a Git-friendly, file-based alternative to Postman that stores each request as a `.bru` text file. Detect `.bru` files by a recognized top-level block (`meta`, `get`, `post`, …) and extract endpoints, query and path params, headers, and JSON / form-urlencoded / multipart-form bodies. Tilde-prefixed (disabled) entries are skipped, and `body:json` blocks are parsed as JSON. Closes #1630
1 parent 8c1bc86 commit b643d82

15 files changed

Lines changed: 432 additions & 0 deletions

File tree

docs/content/usage/supported/specification/index.ko.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ sort_by = "weight"
1414
|---|---|---|---|---|---|---|---|---|
1515
| Asyncapi | JSON | ☑️ | ☑️ ||| ☑️ |||
1616
| Asyncapi | YAML | ☑️ | ☑️ ||| ☑️ |||
17+
| Bruno | BRU | ☑️ | ☑️ | ☑️ | ☑️ | ☑️ | ☑️ ||
1718
| GraphQL | GRAPHQL | ☑️ | ☑️ | ☑️ | ☑️ | ☑️ | ☑️ | ☑️ |
1819
| Grpc | PROTOBUF | ☑️ | ☑️ | ☑️ | ☑️ | ☑️ |||
1920
| HAR | JSON | ☑️ | ☑️ | ☑️ | ☑️ | ☑️ | ☑️ | ☑️ |
@@ -26,3 +27,4 @@ sort_by = "weight"
2627
| Postman Collection | JSON | ☑️ | ☑️ | ☑️ | ☑️ | ☑️ | ☑️ ||
2728
| RAML | YAML | ☑️ | ☑️ | ☑️ | ☑️ | ☑️ | ☑️ | ☑️ |
2829
| TypeSpec | TYPESPEC | ☑️ | ☑️ | ☑️ | ☑️ | ☑️ | ☑️ | ☑️ |
30+
| Wsdl | XML | ☑️ | ☑️ ||| ☑️ | ☑️ ||

docs/content/usage/supported/specification/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Beyond source code analysis, Noir can parse API and data specification formats s
1414
|---|---|---|---|---|---|---|---|---|
1515
| Asyncapi | JSON | ☑️ | ☑️ ||| ☑️ |||
1616
| Asyncapi | YAML | ☑️ | ☑️ ||| ☑️ |||
17+
| Bruno | BRU | ☑️ | ☑️ | ☑️ | ☑️ | ☑️ | ☑️ ||
1718
| GraphQL | GRAPHQL | ☑️ | ☑️ | ☑️ | ☑️ | ☑️ | ☑️ | ☑️ |
1819
| Grpc | PROTOBUF | ☑️ | ☑️ | ☑️ | ☑️ | ☑️ |||
1920
| HAR | JSON | ☑️ | ☑️ | ☑️ | ☑️ | ☑️ | ☑️ | ☑️ |
@@ -26,3 +27,4 @@ Beyond source code analysis, Noir can parse API and data specification formats s
2627
| Postman Collection | JSON | ☑️ | ☑️ | ☑️ | ☑️ | ☑️ | ☑️ ||
2728
| RAML | YAML | ☑️ | ☑️ | ☑️ | ☑️ | ☑️ | ☑️ | ☑️ |
2829
| TypeSpec | TYPESPEC | ☑️ | ☑️ | ☑️ | ☑️ | ☑️ | ☑️ | ☑️ |
30+
| Wsdl | XML | ☑️ | ☑️ ||| ☑️ | ☑️ ||
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
meta {
2+
name: Create User
3+
type: http
4+
seq: 2
5+
}
6+
7+
post {
8+
url: https://api.example.com/users
9+
body: json
10+
}
11+
12+
headers {
13+
Content-Type: application/json
14+
}
15+
16+
body:json {
17+
{
18+
"name": "John Doe",
19+
"email": "john@example.com"
20+
}
21+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
meta {
2+
name: List Users
3+
type: http
4+
seq: 1
5+
}
6+
7+
get {
8+
url: https://api.example.com/users?page=1&limit=10
9+
}
10+
11+
query {
12+
page: 1
13+
limit: 10
14+
~debug: true
15+
}
16+
17+
headers {
18+
Authorization: Bearer token123
19+
Content-Type: application/json
20+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
meta {
2+
name: Update User
3+
type: http
4+
seq: 3
5+
}
6+
7+
put {
8+
url: https://api.example.com/users/:userId
9+
body: form-urlencoded
10+
}
11+
12+
params:path {
13+
userId: 123
14+
}
15+
16+
body:form-urlencoded {
17+
name: Jane Doe
18+
email: jane@example.com
19+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
meta {
2+
name: Delete User
3+
type: http
4+
seq: 2
5+
}
6+
7+
delete {
8+
url: https://api.example.com/users/:id
9+
}
10+
11+
params:path {
12+
id: 1
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
meta {
2+
name: Get User
3+
type: http
4+
seq: 1
5+
}
6+
7+
get {
8+
url: https://api.example.com/users/:id
9+
}
10+
11+
params:path {
12+
id: 1
13+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
meta {
2+
name: Upload File
3+
type: http
4+
seq: 1
5+
}
6+
7+
post {
8+
url: https://api.example.com/upload
9+
body: multipart-form
10+
}
11+
12+
body:multipart-form {
13+
file: @file(./image.png)
14+
title: My File
15+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
require "../../func_spec.cr"
2+
3+
expected_endpoints = [
4+
Endpoint.new("/users", "GET", [
5+
Param.new("Authorization", "Bearer token123", "header"),
6+
Param.new("page", "1", "query"),
7+
Param.new("limit", "10", "query"),
8+
]),
9+
Endpoint.new("/users", "POST", [
10+
Param.new("name", "John Doe", "json"),
11+
Param.new("email", "john@example.com", "json"),
12+
]),
13+
Endpoint.new("/users/:userId", "PUT", [
14+
Param.new("userId", "123", "path"),
15+
Param.new("name", "Jane Doe", "form"),
16+
Param.new("email", "jane@example.com", "form"),
17+
]),
18+
]
19+
20+
FunctionalTester.new("fixtures/specification/bruno/common/", {
21+
:techs => 1,
22+
:endpoints => expected_endpoints.size,
23+
}, expected_endpoints).perform_tests
24+
25+
FunctionalTester.new("fixtures/specification/bruno/folders/", {
26+
:techs => 1,
27+
:endpoints => 2,
28+
}, nil).perform_tests
29+
30+
FunctionalTester.new("fixtures/specification/bruno/forms/", {
31+
:techs => 1,
32+
:endpoints => 1,
33+
}, [
34+
Endpoint.new("/upload", "POST", [
35+
Param.new("file", "@file(./image.png)", "form"),
36+
Param.new("title", "My File", "form"),
37+
]),
38+
]).perform_tests
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
require "../../../spec_helper"
2+
require "../../../../src/detector/detectors/specification/*"
3+
require "../../../../src/models/code_locator"
4+
5+
describe "Detect Bruno Collection" do
6+
options = create_test_options
7+
instance = Detector::Specification::Bruno.new options
8+
9+
it "detects a request file with a method block" do
10+
content = <<-BRU
11+
meta {
12+
name: List Users
13+
type: http
14+
}
15+
16+
get {
17+
url: https://api.example.com/users
18+
}
19+
BRU
20+
21+
instance.detect("list-users.bru", content).should be_true
22+
end
23+
24+
it "ignores non-.bru filenames" do
25+
content = <<-BRU
26+
get {
27+
url: https://api.example.com/users
28+
}
29+
BRU
30+
31+
instance.detect("list-users.txt", content).should be_false
32+
end
33+
34+
it "ignores .bru files without a recognized block header" do
35+
content = <<-BRU
36+
# not a real .bru file
37+
hello world
38+
BRU
39+
40+
instance.detect("notes.bru", content).should be_false
41+
end
42+
43+
it "registers the path in the code locator" do
44+
content = <<-BRU
45+
get {
46+
url: https://api.example.com/health
47+
}
48+
BRU
49+
50+
locator = CodeLocator.instance
51+
locator.clear "bruno-bru"
52+
instance.detect("health.bru", content)
53+
locator.all("bruno-bru").should eq(["health.bru"])
54+
end
55+
end

0 commit comments

Comments
 (0)