winglang / wing

A programming language for the cloud ☁️ A unified programming model, combining infrastructure and runtime code into one language ⚡
https://winglang.io
Other
4.68k stars 181 forks source link

Compilation fails with `Maximum call stack size exceeded` with recursive inflight function #6513

Open skyrpex opened 2 weeks ago

skyrpex commented 2 weeks ago

I tried this:

bring cloud;

struct JsonApiProps {
  api: cloud.Api;
}

struct JsonApiResponse {
  status: num?;
  headers: Map<str>?;
  body: Json?;
}

struct HttpErrorResponse {
  code: num;
  message: str;
}

inflight interface Middleware {
  inflight handle(request: cloud.ApiRequest, next: inflight (cloud.ApiRequest): JsonApiResponse): JsonApiResponse;
}

pub class JsonApi {
  api: cloud.Api;
  pub url: str;
  var handlerCount: num;
  var middlewares: MutArray<Middleware>;

  new(props: JsonApiProps) {
    this.api = props.api;
    this.url = this.api.url;
    this.handlerCount = 0;
    this.middlewares = MutArray<Middleware>[];
  }

  wrapHandler(
    handler: inflight (cloud.ApiRequest): JsonApiResponse,
    middlewares: Array<Middleware>,
  ): inflight (cloud.ApiRequest): cloud.ApiResponse {
    let applyMiddlewares = inflight (request: cloud.ApiRequest, index: num?): JsonApiResponse => {
      log("{index ?? 0}");
      if let middleware = middlewares.tryAt(index ?? 0) {
        let next = (request: cloud.ApiRequest): JsonApiResponse => {
          let newIndex = (index ?? 0) + 1;
          if newIndex < middlewares.length {
            return applyMiddlewares(request, newIndex);
          }
          return handler(request);
        };
        return middleware.handle(request, next);
      }
      return handler(request);
    };
    return inflight (request: cloud.ApiRequest): cloud.ApiResponse => {
      try {
        let response = applyMiddlewares(request);

        let headers = response.headers?.copyMut();
        headers?.set("content-type", "application/json");

        let var bodyStr = "";
        if let body = response.body {
          bodyStr = Json.stringify(body);
        }

        return {
          status: response.status ?? 200,
          headers: headers?.copy(),
          body: bodyStr,
        };
      } catch error {
        if let httpError = HttpErrorResponse.tryFromJson(Json.tryParse(error)) {
          return {
            status: httpError.code,
            headers: {
              "content-type": "application/json",
            },
            body: Json.stringify({
              error: httpError.message,
            }),
          };
        }

        log("Internal server error");
        log(unsafeCast(error));

        return {
          status: 500,
          headers: {
            "content-type": "application/json",
          },
          body: Json.stringify({
            error: "Internal server error",
          }),
        };
      }
    };
  }

  pub addMiddleware(middleware: Middleware) {
    this.middlewares.push(middleware);
  }

  pub get(path: str, handler: inflight (cloud.ApiRequest): JsonApiResponse) {
    this.api.get(path, this.wrapHandler(handler, this.middlewares.copy()));
  }

  pub post(path: str, handler: inflight (cloud.ApiRequest): JsonApiResponse) {
    this.api.post(path, this.wrapHandler(handler, this.middlewares.copy()));
  }

  pub put(path: str, handler: inflight (cloud.ApiRequest): JsonApiResponse) {
    this.api.put(path, this.wrapHandler(handler, this.middlewares.copy()));
  }
}

let json = new JsonApi(
  api: new cloud.Api(),
);

json.addMiddleware(inflight (request, next) => {
  log(unsafeCast(request));
  return next(request);
});

json.get("/", inflight () => {
  return {
    body: {
      message: "hello",
    },
  };
});

This happened:

Compilation fails with Maximum call stack size exceeded.

I expected this:

No response

Is there a workaround?

Using an auxiliary class works:

bring cloud;

struct JsonApiProps {
  api: cloud.Api;
}

struct JsonApiResponse {
  status: num?;
  headers: Map<str>?;
  body: Json?;
}

struct HttpErrorResponse {
  code: num;
  message: str;
}

inflight interface Middleware {
  inflight handle(request: cloud.ApiRequest, next: inflight (cloud.ApiRequest): JsonApiResponse): JsonApiResponse;
}

pub class JsonApi {
  api: cloud.Api;
  pub url: str;
  var handlerCount: num;
  var middlewares: MutArray<Middleware>;

  new(props: JsonApiProps) {
    this.api = props.api;
    this.url = this.api.url;
    this.handlerCount = 0;
    this.middlewares = MutArray<Middleware>[];
  }

  wrapHandler(
    handler: inflight (cloud.ApiRequest): JsonApiResponse,
    middlewares: Array<Middleware>,
  ): inflight (cloud.ApiRequest): cloud.ApiResponse {
    class ApplyMiddleware {
      pub inflight apply(request: cloud.ApiRequest, index: num?): JsonApiResponse  {
        if let middleware = middlewares.tryAt(index ?? 0) {
          let next = (request: cloud.ApiRequest): JsonApiResponse => {
            let newIndex = (index ?? 0) + 1;
            if newIndex < middlewares.length {
              return this.apply(request, newIndex);
            }
            return handler(request);
          };
          return middleware.handle(request, next);
        }
        return handler(request);
      }
    }
    let applyMiddleware = new ApplyMiddleware();
    return inflight (request: cloud.ApiRequest): cloud.ApiResponse => {
      try {
        let response = applyMiddleware.apply(request);

        let headers = response.headers?.copyMut();
        headers?.set("content-type", "application/json");

        let var bodyStr = "";
        if let body = response.body {
          bodyStr = Json.stringify(body);
        }

        return {
          status: response.status ?? 200,
          headers: headers?.copy(),
          body: bodyStr,
        };
      } catch error {
        if let httpError = HttpErrorResponse.tryFromJson(Json.tryParse(error)) {
          return {
            status: httpError.code,
            headers: {
              "content-type": "application/json",
            },
            body: Json.stringify({
              error: httpError.message,
            }),
          };
        }

        log("Internal server error");
        log(unsafeCast(error));

        return {
          status: 500,
          headers: {
            "content-type": "application/json",
          },
          body: Json.stringify({
            error: "Internal server error",
          }),
        };
      }
    };
  }

  pub addMiddleware(middleware: Middleware) {
    this.middlewares.push(middleware);
  }

  pub get(path: str, handler: inflight (cloud.ApiRequest): JsonApiResponse) {
    this.api.get(path, this.wrapHandler(handler, this.middlewares.copy()));
  }

  pub post(path: str, handler: inflight (cloud.ApiRequest): JsonApiResponse) {
    this.api.post(path, this.wrapHandler(handler, this.middlewares.copy()));
  }

  pub put(path: str, handler: inflight (cloud.ApiRequest): JsonApiResponse) {
    this.api.put(path, this.wrapHandler(handler, this.middlewares.copy()));
  }
}

let json = new JsonApi(
  api: new cloud.Api(),
);

json.addMiddleware(inflight (request, next) => {
  log(unsafeCast(request));
  return next(request);
});

json.get("/", inflight () => {
  return {
    body: {
      message: "hello",
    },
  };
});

Anything else?

No response

Wing Version

0.73.52

Node.js Version

No response

Platform(s)

No response

Community Notes