Skip to content

Commit a8525ae

Browse files
authored
Add ServeMuxVisitor for Go and emit securitySchemes in OpenAPIV3Visitor (#60)
1 parent 33d0f01 commit a8525ae

File tree

6 files changed

+344
-12
lines changed

6 files changed

+344
-12
lines changed

src/go/constant.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,20 +34,24 @@ export const translations: Map<string, string> = new Map<string, string>([
3434

3535
export const IMPORTS = {
3636
context: "context",
37+
embed: "embed",
3738
errors: "errors",
3839
json: "encoding/json",
3940
net: "net",
4041
os: "os",
42+
http: "net/http",
4143
fiber: "github.com/gofiber/fiber/v2",
42-
tfiber: "github.com/apexlang/api-go/transport/tfiber",
43-
httpresponse: "github.com/apexlang/api-go/transport/httpresponse",
4444
emptypb: "google.golang.org/protobuf/types/known/emptypb",
45-
errorz: "github.com/apexlang/api-go/errorz",
46-
convert: "github.com/apexlang/api-go/convert",
4745
timestamppb: "google.golang.org/protobuf/types/known/timestamppb",
4846
wrapperspb: "google.golang.org/protobuf/types/known/wrapperspb",
4947
grpc: "google.golang.org/grpc",
48+
convert: "github.com/apexlang/api-go/convert",
49+
errorz: "github.com/apexlang/api-go/errorz",
50+
httpresponse: "github.com/apexlang/api-go/transport/httpresponse",
51+
authorization: "github.com/apexlang/api-go/transport/authorization",
52+
tfiber: "github.com/apexlang/api-go/transport/tfiber",
5053
tgrpc: "github.com/apexlang/api-go/transport/tgrpc",
54+
thttp: "github.com/apexlang/api-go/transport/thttp",
5155
msgpack: "github.com/wapc/tinygo-msgpack",
5256
msgpackconvert: ["convert", "github.com/wapc/tinygo-msgpack/convert"],
5357
zap: "go.uber.org/zap",

src/go/embed_visitor.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Context } from "../../deps/@apexlang/core/model/mod.ts";
2+
import { getImports, GoVisitor } from "./go_visitor.ts";
3+
4+
interface Embed {
5+
path: string;
6+
var: string;
7+
type: string;
8+
}
9+
10+
export class EmbedVisitor extends GoVisitor {
11+
public override visitInterfacesBefore(context: Context): void {
12+
const importer = getImports(context);
13+
const config = context.config.embed as Embed[];
14+
15+
if (config && config.length && config.length > 0) {
16+
importer.stdlib("embed", "_");
17+
config.forEach((value) => {
18+
this.write(`//go:embed ${value.path}
19+
var ${value.var} ${value.type}\n`);
20+
});
21+
this.write(`\n`);
22+
}
23+
}
24+
}

src/go/mod.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
export * from "./go_visitor.ts";
1818
export * from "./alias_visitor.ts";
1919
export * from "./constant.ts";
20+
export * from "./embed_visitor.ts";
2021
export * from "./enum_visitor.ts";
2122
export * from "./fiber_visitor.ts";
2223
export * from "./grpc_visitor.ts";
@@ -26,6 +27,7 @@ export * from "./interfaces_visitor.ts";
2627
export * from "./main_visitor.ts";
2728
export * from "./scaffold_visitor.ts";
2829
export * from "./struct_visitor.ts";
30+
export * from "./servemux_visitor.ts";
2931
export * from "./union_visitor.ts";
3032
export * from "./msgpack_visitor.ts";
3133
export * from "./msgpack_constants.ts";

src/go/servemux_visitor.ts

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
import {
2+
Annotation,
3+
AnyType,
4+
Context,
5+
Kind,
6+
Type,
7+
} from "../../deps/@apexlang/core/model/mod.ts";
8+
import {
9+
capitalize,
10+
convertOperationToType,
11+
isKinds,
12+
isObject,
13+
isService,
14+
unwrapKinds,
15+
} from "../utils/mod.ts";
16+
import { getMethods, getPath, hasBody, ScopesDirective } from "../rest/mod.ts";
17+
import { StructVisitor } from "./struct_visitor.ts";
18+
import { expandType, fieldName, methodName } from "./helpers.ts";
19+
import { translateAlias } from "./alias_visitor.ts";
20+
import { getImporter, GoVisitor } from "./go_visitor.ts";
21+
import { IMPORTS } from "./constant.ts";
22+
23+
export class ServeMuxVisitor extends GoVisitor {
24+
public override visitInterfaceBefore(context: Context): void {
25+
if (!isService(context)) {
26+
return;
27+
}
28+
29+
const { interface: iface } = context;
30+
const visitor = new ServeMuxServiceVisitor(this.writer);
31+
iface.accept(context, visitor);
32+
}
33+
}
34+
35+
class ServeMuxServiceVisitor extends GoVisitor {
36+
public override visitInterfaceBefore(context: Context): void {
37+
const { interface: iface } = context;
38+
const $ = getImporter(context, IMPORTS);
39+
this
40+
.write(
41+
`func ${iface.name}ServeMux(service ${iface.name}) func(*${$.http}.ServeMux) {
42+
return func(mux *${$.http}.ServeMux) {\n`,
43+
);
44+
}
45+
46+
public override visitOperation(context: Context): void {
47+
const { interface: iface, operation } = context;
48+
const $ = getImporter(context, IMPORTS);
49+
const path = getPath(context);
50+
if (path == "") {
51+
return;
52+
}
53+
const methods = getMethods(operation).map((m) =>
54+
capitalize(m.toLowerCase())
55+
);
56+
const translate = translateAlias(context);
57+
58+
let scopes: string[] = [];
59+
iface.annotation("scopes", (a) => {
60+
scopes = getScopes(a);
61+
});
62+
// Operation scopes override interface scopes
63+
operation.annotation("scopes", (a) => {
64+
scopes = getScopes(a);
65+
});
66+
67+
methods.forEach((method) => {
68+
let paramType: AnyType | undefined;
69+
this.write(
70+
`mux.HandleFunc("${method.toUpperCase()} ${path}", func(w ${$.http}.ResponseWriter, r *${$.http}.Request) {\n`,
71+
);
72+
73+
if (scopes.length > 0) {
74+
this.write(
75+
`if err := ${$.authorization}.CheckScopes(r.Context(), "write:clusters"); err != nil {
76+
${$.thttp}.Error(w, nil, err, ${$.errorz}.PermissionDenied)
77+
return
78+
}\n`,
79+
);
80+
}
81+
82+
this.write(`resp := ${$.httpresponse}.New()
83+
ctx := ${$.httpresponse}.NewContext(r.Context(), resp)\n`);
84+
if (operation.isUnary()) {
85+
// TODO: check type
86+
paramType = operation.parameters[0].type;
87+
} else if (operation.parameters.length > 0) {
88+
const argsType = convertOperationToType(
89+
context.getType.bind(context),
90+
iface,
91+
operation,
92+
);
93+
paramType = argsType;
94+
const structVisitor = new StructVisitor(this.writer);
95+
argsType.accept(context.clone({ type: argsType }), structVisitor);
96+
}
97+
98+
const operMethod = methodName(operation, operation.name);
99+
100+
if (paramType) {
101+
// TODO
102+
this.write(
103+
`var args ${expandType(paramType, undefined, false, translate)}\n`,
104+
);
105+
if (hasBody(method)) {
106+
this.write(
107+
`if err := ${$.json}.NewDecoder(r.Body).Decode(&args); err != nil {
108+
${$.thttp}.Error(w, resp, err, ${$.errorz}.Internal)
109+
return
110+
}\n`,
111+
);
112+
}
113+
114+
switch (paramType.kind) {
115+
case Kind.Type: {
116+
let foundQuery = false;
117+
const t = paramType as Type;
118+
t.fields.forEach((f) => {
119+
if (path.indexOf(`{${f.name}}`) != -1) {
120+
// Set path argument
121+
this.write(
122+
`args.${fieldName(f, f.name)} = r.PathValue("${f.name}")\n`,
123+
);
124+
} else if (f.annotation("query") != undefined) {
125+
if (!foundQuery) {
126+
this.write(`query := r.URL.Query()\n`);
127+
foundQuery = true;
128+
}
129+
this.write(
130+
`args.${fieldName(f, f.name)} = query.Get("${f.name}")\n`,
131+
);
132+
}
133+
});
134+
135+
break;
136+
}
137+
}
138+
139+
if (operation.type.kind != Kind.Void) {
140+
this.write(`result, `);
141+
}
142+
if (operation.isUnary()) {
143+
const pt = unwrapKinds(paramType, Kind.Alias);
144+
const share = isKinds(pt, Kind.Primitive, Kind.Enum) ? "" : "&";
145+
this.write(`err := service.${operMethod}(ctx, ${share}args)\n`);
146+
} else {
147+
const args = (paramType as Type).fields
148+
.map(
149+
(f) =>
150+
`, ${isObject(f.type, false) ? "&" : ""}args.${
151+
fieldName(
152+
f,
153+
f.name,
154+
)
155+
}`,
156+
)
157+
.join("");
158+
this.write(`err := service.${operMethod}(ctx${args})\n`);
159+
}
160+
} else {
161+
this.write(`err := service.${operMethod}(ctx)\n`);
162+
}
163+
164+
if (operation.type.kind != Kind.Void) {
165+
this.write(`${$.thttp}.Response(w, resp, result, err)\n`);
166+
} else {
167+
this.write(`${$.thttp}.NoContent(w, resp, err)\n`);
168+
}
169+
this.write(`})\n`);
170+
});
171+
}
172+
173+
public override visitInterfaceAfter(_context: Context): void {
174+
this.write(` }
175+
}\n`);
176+
}
177+
}
178+
179+
function getScopes(a: Annotation): string[] {
180+
let scopes = a.convert<ScopesDirective>().value;
181+
// Convert single value to array
182+
if (typeof scopes === "string") {
183+
scopes = [scopes as string];
184+
}
185+
return scopes || [];
186+
}

0 commit comments

Comments
 (0)