Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions src/go/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,24 @@ export const translations: Map<string, string> = new Map<string, string>([

export const IMPORTS = {
context: "context",
embed: "embed",
errors: "errors",
json: "encoding/json",
net: "net",
os: "os",
http: "net/http",
fiber: "github.com/gofiber/fiber/v2",
tfiber: "github.com/apexlang/api-go/transport/tfiber",
httpresponse: "github.com/apexlang/api-go/transport/httpresponse",
emptypb: "google.golang.org/protobuf/types/known/emptypb",
errorz: "github.com/apexlang/api-go/errorz",
convert: "github.com/apexlang/api-go/convert",
timestamppb: "google.golang.org/protobuf/types/known/timestamppb",
wrapperspb: "google.golang.org/protobuf/types/known/wrapperspb",
grpc: "google.golang.org/grpc",
convert: "github.com/apexlang/api-go/convert",
errorz: "github.com/apexlang/api-go/errorz",
httpresponse: "github.com/apexlang/api-go/transport/httpresponse",
authorization: "github.com/apexlang/api-go/transport/authorization",
tfiber: "github.com/apexlang/api-go/transport/tfiber",
tgrpc: "github.com/apexlang/api-go/transport/tgrpc",
thttp: "github.com/apexlang/api-go/transport/thttp",
msgpack: "github.com/wapc/tinygo-msgpack",
msgpackconvert: ["convert", "github.com/wapc/tinygo-msgpack/convert"],
zap: "go.uber.org/zap",
Expand Down
24 changes: 24 additions & 0 deletions src/go/embed_visitor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Context } from "../../deps/@apexlang/core/model/mod.ts";
import { getImports, GoVisitor } from "./go_visitor.ts";

interface Embed {
path: string;
var: string;
type: string;
}

export class EmbedVisitor extends GoVisitor {
public override visitInterfacesBefore(context: Context): void {
const importer = getImports(context);
const config = context.config.embed as Embed[];

if (config && config.length && config.length > 0) {
importer.stdlib("embed", "_");
config.forEach((value) => {
this.write(`//go:embed ${value.path}
var ${value.var} ${value.type}\n`);
});
this.write(`\n`);
}
}
}
2 changes: 2 additions & 0 deletions src/go/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
export * from "./go_visitor.ts";
export * from "./alias_visitor.ts";
export * from "./constant.ts";
export * from "./embed_visitor.ts";
export * from "./enum_visitor.ts";
export * from "./fiber_visitor.ts";
export * from "./grpc_visitor.ts";
Expand All @@ -26,6 +27,7 @@ export * from "./interfaces_visitor.ts";
export * from "./main_visitor.ts";
export * from "./scaffold_visitor.ts";
export * from "./struct_visitor.ts";
export * from "./servemux_visitor.ts";
export * from "./union_visitor.ts";
export * from "./msgpack_visitor.ts";
export * from "./msgpack_constants.ts";
Expand Down
186 changes: 186 additions & 0 deletions src/go/servemux_visitor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import {
Annotation,
AnyType,
Context,
Kind,
Type,
} from "../../deps/@apexlang/core/model/mod.ts";
import {
capitalize,
convertOperationToType,
isKinds,
isObject,
isService,
unwrapKinds,
} from "../utils/mod.ts";
import { getMethods, getPath, hasBody, ScopesDirective } from "../rest/mod.ts";
import { StructVisitor } from "./struct_visitor.ts";
import { expandType, fieldName, methodName } from "./helpers.ts";
import { translateAlias } from "./alias_visitor.ts";
import { getImporter, GoVisitor } from "./go_visitor.ts";
import { IMPORTS } from "./constant.ts";

export class ServeMuxVisitor extends GoVisitor {
public override visitInterfaceBefore(context: Context): void {
if (!isService(context)) {
return;
}

const { interface: iface } = context;
const visitor = new ServeMuxServiceVisitor(this.writer);
iface.accept(context, visitor);
}
}

class ServeMuxServiceVisitor extends GoVisitor {
public override visitInterfaceBefore(context: Context): void {
const { interface: iface } = context;
const $ = getImporter(context, IMPORTS);
this
.write(
`func ${iface.name}ServeMux(service ${iface.name}) func(*${$.http}.ServeMux) {
return func(mux *${$.http}.ServeMux) {\n`,
);
}

public override visitOperation(context: Context): void {
const { interface: iface, operation } = context;
const $ = getImporter(context, IMPORTS);
const path = getPath(context);
if (path == "") {
return;
}
const methods = getMethods(operation).map((m) =>
capitalize(m.toLowerCase())
);
const translate = translateAlias(context);

let scopes: string[] = [];
iface.annotation("scopes", (a) => {
scopes = getScopes(a);
});
// Operation scopes override interface scopes
operation.annotation("scopes", (a) => {
scopes = getScopes(a);
});

methods.forEach((method) => {
let paramType: AnyType | undefined;
this.write(
`mux.HandleFunc("${method.toUpperCase()} ${path}", func(w ${$.http}.ResponseWriter, r *${$.http}.Request) {\n`,
);

if (scopes.length > 0) {
this.write(
`if err := ${$.authorization}.CheckScopes(r.Context(), "write:clusters"); err != nil {
${$.thttp}.Error(w, nil, err, ${$.errorz}.PermissionDenied)
return
}\n`,
);
}

this.write(`resp := ${$.httpresponse}.New()
ctx := ${$.httpresponse}.NewContext(r.Context(), resp)\n`);
if (operation.isUnary()) {
// TODO: check type
paramType = operation.parameters[0].type;
} else if (operation.parameters.length > 0) {
const argsType = convertOperationToType(
context.getType.bind(context),
iface,
operation,
);
paramType = argsType;
const structVisitor = new StructVisitor(this.writer);
argsType.accept(context.clone({ type: argsType }), structVisitor);
}

const operMethod = methodName(operation, operation.name);

if (paramType) {
// TODO
this.write(
`var args ${expandType(paramType, undefined, false, translate)}\n`,
);
if (hasBody(method)) {
this.write(
`if err := ${$.json}.NewDecoder(r.Body).Decode(&args); err != nil {
${$.thttp}.Error(w, resp, err, ${$.errorz}.Internal)
return
}\n`,
);
}

switch (paramType.kind) {
case Kind.Type: {
let foundQuery = false;
const t = paramType as Type;
t.fields.forEach((f) => {
if (path.indexOf(`{${f.name}}`) != -1) {
// Set path argument
this.write(
`args.${fieldName(f, f.name)} = r.PathValue("${f.name}")\n`,
);
} else if (f.annotation("query") != undefined) {
if (!foundQuery) {
this.write(`query := r.URL.Query()\n`);
foundQuery = true;
}
this.write(
`args.${fieldName(f, f.name)} = query.Get("${f.name}")\n`,
);
}
});

break;
}
}

if (operation.type.kind != Kind.Void) {
this.write(`result, `);
}
if (operation.isUnary()) {
const pt = unwrapKinds(paramType, Kind.Alias);
const share = isKinds(pt, Kind.Primitive, Kind.Enum) ? "" : "&";
this.write(`err := service.${operMethod}(ctx, ${share}args)\n`);
} else {
const args = (paramType as Type).fields
.map(
(f) =>
`, ${isObject(f.type, false) ? "&" : ""}args.${
fieldName(
f,
f.name,
)
}`,
)
.join("");
this.write(`err := service.${operMethod}(ctx${args})\n`);
}
} else {
this.write(`err := service.${operMethod}(ctx)\n`);
}

if (operation.type.kind != Kind.Void) {
this.write(`${$.thttp}.Response(w, resp, result, err)\n`);
} else {
this.write(`${$.thttp}.NoContent(w, resp, err)\n`);
}
this.write(`})\n`);
});
}

public override visitInterfaceAfter(_context: Context): void {
this.write(` }
}\n`);
}
}

function getScopes(a: Annotation): string[] {
let scopes = a.convert<ScopesDirective>().value;
// Convert single value to array
if (typeof scopes === "string") {
scopes = [scopes as string];
}
return scopes || [];
}
Loading
Loading