Skip to content

Commit c1db132

Browse files
authored
refactor(cosmosgen): generate openapi for external modules (#4741)
* refactor(cosmosgen): generate openapi for external modules * cl * use json
1 parent 9ef87ad commit c1db132

File tree

18 files changed

+95
-23
lines changed

18 files changed

+95
-23
lines changed

changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
- [#4717](https://github.com/ignite/cli/pull/4717) Bump Cosmos SDK to `v0.53.2`.
1616
- [#4718](https://github.com/ignite/cli/pull/4718) Bump default Ignite Apps.
17+
- [#4741](https://github.com/ignite/cli/pull/4741) Let `generate openapi` generate external modules OpenAPI spec.
1718
- [#4747](https://github.com/ignite/cli/pull/4747) Improve Ignite UI.
1819

1920
### Fixes

docs/docs/08-configuration/01-config.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ paths where the client-side code is generated.
264264
```yml
265265
client:
266266
openapi:
267-
path: "docs/static/openapi.yml"
267+
path: "docs/static/openapi.json"
268268
typescript:
269269
path: "ts-client"
270270
composables:

docs/versioned_docs/version-v29/08-configuration/01-config.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ paths where the client-side code is generated.
264264
```yml
265265
client:
266266
openapi:
267-
path: "docs/static/openapi.yml"
267+
path: "docs/static/openapi.json"
268268
typescript:
269269
path: "ts-client"
270270
composables:

go.mod

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ require (
1717
github.com/charmbracelet/fang v0.2.0
1818
github.com/charmbracelet/glow v1.5.1
1919
github.com/charmbracelet/lipgloss v1.1.0
20+
github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.1
2021
github.com/cockroachdb/errors v1.12.0
2122
github.com/cometbft/cometbft v0.38.17
2223
github.com/cosmos/cosmos-sdk v0.53.2
@@ -143,7 +144,6 @@ require (
143144
github.com/charmbracelet/charm v0.8.7 // indirect
144145
github.com/charmbracelet/colorprofile v0.3.0 // indirect
145146
github.com/charmbracelet/glamour v0.6.0 // indirect
146-
github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.1 // indirect
147147
github.com/charmbracelet/x/ansi v0.8.0 // indirect
148148
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
149149
github.com/charmbracelet/x/exp/charmtone v0.0.0-20250603201427-c31516f43444 // indirect
@@ -281,6 +281,7 @@ require (
281281
github.com/gostaticanalysis/nilerr v0.1.1 // indirect
282282
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
283283
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
284+
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect
284285
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
285286
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
286287
github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect
@@ -505,6 +506,7 @@ require (
505506
tool (
506507
github.com/bufbuild/buf/cmd/buf
507508
github.com/golangci/golangci-lint/cmd/golangci-lint
509+
github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2
508510
github.com/tbruyelle/mdgofmt/cmd/mdgofmt
509511
github.com/vektra/mockery/v2
510512
golang.org/x/tools/cmd/goimports

ignite/cmd/scaffold.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const (
2424
flagDescription = "desc"
2525
flagProtoDir = "proto-dir"
2626

27-
msgCommitPrefix = "Your saved project changes have not been committed.\nTo enable reverting to your current state, commit your saved changes."
27+
msgCommitPrefix = "Your project changes have not been committed.\nTo enable reverting to your current state, commit your saved changes."
2828
msgCommitPrompt = "Do you want to proceed without committing your saved changes"
2929

3030
statusScaffolding = "Scaffolding..."

ignite/config/chain/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ var (
4343

4444
// DefaultOpenAPIPath defines the default relative path to use when generating an OpenAPI schema.
4545
// The path is relative to the app's directory.
46-
DefaultOpenAPIPath = "docs/static/openapi.yml"
46+
DefaultOpenAPIPath = "docs/static/openapi.json"
4747

4848
// LatestVersion defines the latest version of the config.
4949
LatestVersion version.Version = 1

ignite/internal/tools/gen-mig-diffs/pkg/diff/diff.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ var diffIgnoreGlobs = []string{
2323
"**.pulsar.go",
2424
"**/node_modules/**",
2525
"**/openapi.yml",
26+
"**/openapi.json",
2627
".gitignore",
2728
".github/**",
2829
"**.html",

ignite/pkg/cosmosgen/generate_openapi.go

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -127,21 +127,17 @@ func (g *generator) generateOpenAPISpec(ctx context.Context) error {
127127

128128
doneMods := make(map[string]struct{})
129129
for _, modules := range g.thirdModules {
130-
if len(modules) == 0 {
131-
continue
132-
}
133-
var (
134-
m = modules[0]
135-
path = extractRootModulePath(m.Pkg.Path)
136-
)
130+
for _, m := range modules {
131+
path := extractRootModulePath(m.Pkg.Path)
137132

138-
if _, ok := doneMods[path]; ok {
139-
continue
140-
}
141-
doneMods[path] = struct{}{}
133+
if _, ok := doneMods[path]; ok {
134+
continue
135+
}
136+
doneMods[path] = struct{}{}
142137

143-
if err := gen(path, "", m.Name); err != nil {
144-
return err
138+
if err := gen(path, "proto", m.Name); err != nil {
139+
return err
140+
}
145141
}
146142
}
147143

ignite/pkg/cosmosgen/generate_openapi_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
package cosmosgen
22

33
import (
4+
"os"
5+
"path/filepath"
46
"testing"
57

8+
"github.com/ignite/cli/v29/ignite/pkg/cache"
9+
"github.com/ignite/cli/v29/ignite/pkg/cosmosanalysis/module"
10+
"github.com/ignite/cli/v29/ignite/pkg/cosmosbuf"
11+
"github.com/ignite/cli/v29/ignite/pkg/dirchange"
12+
"github.com/ignite/cli/v29/ignite/pkg/errors"
613
"github.com/stretchr/testify/require"
714
)
815

@@ -55,3 +62,47 @@ func Test_extractRootModulePath(t *testing.T) {
5562
})
5663
}
5764
}
65+
66+
func TestGenerateOpenAPI(t *testing.T) {
67+
require := require.New(t)
68+
testdataDir := "testdata"
69+
appDir := filepath.Join(testdataDir, "testchain")
70+
openAPIFile := filepath.Join(appDir, "docs", "static", "openapi.json")
71+
72+
cacheStorage, err := cache.NewStorage(filepath.Join(t.TempDir(), "cache.db"))
73+
require.NoError(err)
74+
75+
buf, err := cosmosbuf.New(cacheStorage, t.Name())
76+
require.NoError(err)
77+
78+
// Use module discovery to collect test module proto.
79+
m, err := module.Discover(t.Context(), appDir, appDir, module.WithProtoDir("proto"))
80+
require.NoError(err, "failed to discover module")
81+
require.Len(m, 1, "expected exactly one module to be discovered")
82+
83+
g := &generator{
84+
appPath: appDir,
85+
protoDir: "proto",
86+
goModPath: "go.mod",
87+
cacheStorage: cacheStorage,
88+
buf: buf,
89+
appModules: m,
90+
opts: &generateOptions{
91+
specOut: openAPIFile,
92+
},
93+
}
94+
95+
err = g.generateOpenAPISpec(t.Context())
96+
if err != nil && !errors.Is(err, dirchange.ErrNoFile) {
97+
require.NoError(err, "failed to generate OpenAPI spec")
98+
}
99+
100+
// compare generated OpenAPI spec with golden files
101+
goldenFile := filepath.Join(testdataDir, "expected_files", "openapi", "openapi.json")
102+
gold, err := os.ReadFile(goldenFile)
103+
require.NoError(err, "failed to read golden file: %s", goldenFile)
104+
105+
gotBytes, err := os.ReadFile(openAPIFile)
106+
require.NoError(err, "failed to read generated file: %s", openAPIFile)
107+
require.Equal(string(gold), string(gotBytes), "generated OpenAPI spec does not match golden file")
108+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"id":"go.mod","consumes":["application/json"],"produces":["application/json"],"swagger":"2.0","info":{"description":"Chain go.mod REST API","title":"HTTP API Console","contact":{"name":"go.mod"},"version":"version not set"},"paths":{"/ignite.planet.mars.Msg/Bar":{"post":{"tags":["Msg"],"operationId":"GoModMsg_Bar","parameters":[{"name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/ignite.planet.mars.MsgBarRequest"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/ignite.planet.mars.MsgBarResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}}}},"/ignite.planet.mars.Msg/MyMessage":{"post":{"tags":["Msg"],"operationId":"GoModMsg_MyMessage","parameters":[{"name":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/ignite.planet.mars.MsgMyMessageRequest"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/ignite.planet.mars.MsgMyMessageResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}}}},"/ignite/mars/query_simple":{"get":{"tags":["Query"],"operationId":"GoModQuery_QuerySimple","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/ignite.planet.mars.QuerySimpleResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}}}},"/ignite/mars/query_simple/{mytypefield}":{"get":{"tags":["Query"],"operationId":"GoModQuery_QuerySimpleParams","parameters":[{"type":"string","name":"mytypefield","in":"path","required":true}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/ignite.planet.mars.QuerySimpleParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}}}},"/ignite/mars/query_with_params/{mytypefield}":{"get":{"tags":["Query"],"operationId":"GoModQuery_QueryParamsWithPagination","parameters":[{"type":"string","name":"mytypefield","in":"path","required":true},{"type":"string","format":"byte","description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","name":"pagination.key","in":"query"},{"type":"string","format":"uint64","description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","name":"pagination.offset","in":"query"},{"type":"string","format":"uint64","description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","name":"pagination.limit","in":"query"},{"type":"boolean","description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","name":"pagination.count_total","in":"query"},{"type":"boolean","description":"reverse is set to true if results are to be returned in the descending order.","name":"pagination.reverse","in":"query"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/ignite.planet.mars.QueryWithPaginationResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}}}},"/ignite/mars/query_with_query_params/{mytypefield}":{"get":{"tags":["Query"],"operationId":"GoModQuery_QueryWithQueryParamsWithPagination","parameters":[{"type":"string","name":"mytypefield","in":"path","required":true},{"type":"string","name":"query_param","in":"query"},{"type":"string","format":"byte","description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","name":"pagination.key","in":"query"},{"type":"string","format":"uint64","description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","name":"pagination.offset","in":"query"},{"type":"string","format":"uint64","description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","name":"pagination.limit","in":"query"},{"type":"boolean","description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","name":"pagination.count_total","in":"query"},{"type":"boolean","description":"reverse is set to true if results are to be returned in the descending order.","name":"pagination.reverse","in":"query"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/ignite.planet.mars.QueryWithQueryParamsWithPaginationResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}}}},"/ignite/mars/query_with_query_params/{mytypefield}/{mybool}":{"get":{"tags":["Query"],"operationId":"GoModQuery_QueryWithQueryParams","parameters":[{"type":"string","name":"mytypefield","in":"path","required":true},{"type":"boolean","name":"mybool","in":"path","required":true},{"type":"string","name":"query_param","in":"query"},{"type":"array","items":{"type":"boolean"},"collectionFormat":"multi","name":"myrepeatedbool","in":"query"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/ignite.planet.mars.QueryWithQueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}}}}},"definitions":{"cosmos.base.query.v1beta1.PageRequest":{"description":"message SomeRequest {\n Foo some_parameter = 1;\n PageRequest pagination = 2;\n }","type":"object","title":"PageRequest is to be embedded in gRPC request messages for efficient\npagination. Ex:","properties":{"count_total":{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","type":"boolean"},"key":{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","type":"string","format":"byte"},"limit":{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","type":"string","format":"uint64"},"offset":{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","type":"string","format":"uint64"},"reverse":{"description":"reverse is set to true if results are to be returned in the descending order.","type":"boolean"}}},"cosmos.base.query.v1beta1.PageResponse":{"description":"PageResponse is to be embedded in gRPC response messages where the\ncorresponding request message has used PageRequest.\n\n message SomeResponse {\n repeated Bar results = 1;\n PageResponse page = 2;\n }","type":"object","properties":{"next_key":{"description":"next_key is the key to be passed to PageRequest.key to\nquery the next page most efficiently. It will be empty if\nthere are no more results.","type":"string","format":"byte"},"total":{"type":"string","format":"uint64","title":"total is total number of results available if PageRequest.count_total\nwas set, its value is undefined otherwise"}}},"google.protobuf.Any":{"type":"object","properties":{"@type":{"type":"string"}},"additionalProperties":{}},"google.rpc.Status":{"type":"object","properties":{"code":{"type":"integer","format":"int32"},"details":{"type":"array","items":{"type":"object","$ref":"#/definitions/google.protobuf.Any"}},"message":{"type":"string"}}},"ignite.planet.mars.MsgBarRequest":{"type":"object","properties":{"mytypefield":{"type":"string"}}},"ignite.planet.mars.MsgBarResponse":{"type":"object","properties":{"mytypefield":{"type":"string"}}},"ignite.planet.mars.MsgMyMessageRequest":{"type":"object","properties":{"mytypefield":{"type":"string"}}},"ignite.planet.mars.MsgMyMessageResponse":{"type":"object","properties":{"mytypefield":{"type":"string"}}},"ignite.planet.mars.QuerySimpleParamsResponse":{"type":"object","properties":{"bar":{"type":"string"}}},"ignite.planet.mars.QuerySimpleResponse":{"type":"object","properties":{"bar":{"type":"string"}}},"ignite.planet.mars.QueryWithPaginationResponse":{"type":"object","properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}}},"ignite.planet.mars.QueryWithQueryParamsResponse":{"type":"object","properties":{"bar":{"type":"string"}}},"ignite.planet.mars.QueryWithQueryParamsWithPaginationResponse":{"type":"object","properties":{"bar":{"type":"string"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}}}},"tags":[{"name":"Msg"},{"name":"Query"}]}

0 commit comments

Comments
 (0)