moonapi — a typed web framework for MoonBit (\u2190 FastAPI): routing, typed extractors, descriptor-driven validation, multi-version OpenAPI/Swagger (2.0 / 3.0 / 3.1) with security schemes, dependency injection, OAuth2 password-bearer with scopes, per-operation security enforcement, multipart and urlencoded form extractors, response_model filtering, background tasks, sub-application mounting, a CORS/gzip/exception-handler middleware stack with per-status handlers, Server-Sent Events, and WebSocket routes, on the moonasgi SEAM.
Dependencies
Moved on mooncakes from Lfan-ke/moonapi to moonbitstack/moonapi.
flowchart LR
routes["typed routes<br/>App::get / post / …"] --> app["**moonapi** App"]
app -->|"App::to_asgi()"| asgi(["moonasgi AsgiApp"])
app -->|"App::openapi()"| spec["OpenAPI 2.0 / 3.0 / 3.1"]
asgi --> cat["mooncat serves it"]async fn main {
let app = @moonapi.App::new()
app.get("/", _ctx => @moonapi.text(200, "Hello from moonapi!"))
app.get("/users/:id", ctx => @moonapi.text(
200,
"user " + ctx.param("id").unwrap(),
), summary="fetch a user")
app.post("/users", _ctx => @moonapi.text(201, "created"))
// One set of routes, every mainstream spec version.
println(app.openapi_json(version=OpenApi31)) // OpenAPI 3.1.0
println(app.openapi_json(version=OpenApi30)) // OpenAPI 3.0.3
println(app.openapi_json(version=Swagger20)) // Swagger 2.0
@mooncat.serve(app.to_asgi(), port=12000) catch {
err => println("stopped: \{err}")
}
}moon run src/main --target native
curl http://127.0.0.1:12000/users/233| Package | What it holds | Depends on |
|---|---|---|
| moonapi | App and its methods, the façade | everything below |
| moonapi/route | Method, Route, Router, path matching | ctx, endpoint, security, socket |
| moonapi/openapi | the document's info block, routes as operations | route, schema |
| moonapi/security | schemes, guards, per-operation requirements, tokens | ctx, schema, status |
| moonapi/mid | CORS, gzip, exception handlers | ctx |
| moonapi/endpoint | Param, ResponseSpec, Endpoint and its validation | ctx, schema |
| moonapi/ctx | Context and every extractor, background tasks | schema, status |
| moonapi/schema | Schema, constraints, ValidationError, of_model | status |
| moonapi/socket | WebSocket connections and routes | nothing |
| moonapi/status | response helpers, cookies, SSE, status constants | nothing |
| moonapi/di | dependency injection | nothing |
ctx.query("tag") // the preset bound
ctx.query("tag", limits=@moonurl.Limits::new(pairs=100000))
ctx.form(limits=@moonmime.Limits::new(part_size=8 << 20)) // an upload endpoint
@moonapi.create_access_token(sub, secret, now, extra={ "tenant": Json::string("acme") })
@moonapi.sse_response(events, headers=[("cache-control", "no-store")])
@moonapi.sse_response(events, space=false)| Setting | Default | Why that one |
|---|---|---|
| query_limits.pairs | 1000 | qs, and therefore Express, allows a thousand query parameters |
| query_limits.size | 64 KiB | Well past the 8 KiB the common servers allow a whole request line |
| form's limits | @moonmime.limits | Starlette's max_files and max_fields, with python-multipart's part size |
| expires_in_secs | 3600 | The hour every OAuth2 example issues |
| space on a stream | on | The space after a colon is universal on the wire and stripped by every reader |
| Function | Who wins by default | What happens when both speak |
|---|---|---|
| create_access_token | the arguments | aborts — a token whose subject is not the subject passed in is a mistake in the program |
| sse_response | the caller's header | replaces ours, so a response never carries two content-type headers |
pub struct App {
routes : Array[Route]
ws_routes : Array[WsRoute]
middlewares : Array[((Request) -> Response) -> ((Request) -> Response)]
exception_handlers : Array[(Context, Error) -> Response?]
security_schemes : Array[DeclaredScheme]
enforcers : Map[String, (Context, SecurityScopes, Int64) -> Result[AuthenticatedUser, Response]]
status_handlers : Map[Int, (Context) -> Response]
mounts : Array[(String, Mount)]
startup_hooks : Array[() -> Unit raise]
shutdown_hooks : Array[() -> Unit raise]
info : ApiInfo
clock : () -> Int64
deps : Deps?
}fn App::add_security_scheme(self : App, name : String, scheme : SecurityScheme, description? : String) -> Unitfn App::delete(self : App, path : String, handler : (Context) -> Response raise, summary? : String, description? : String, tags? : Array[String], deprecated? : Bool, operation_id? : String, status_code? : Int, responses? : Array[ResponseSpec], name? : String, endpoint? : Endpoint, security? : Array[SecurityRequirement], dependencies? : Array[String], include_in_schema? : Bool, validate? : Bool, openapi_extra? : Json) -> Unitfn App::enable_docs(self : App, openapi_url? : String?, docs_url? : String?, redoc_url? : String?, version? : OpenApiVersion) -> Unitfn App::get(self : App, path : String, handler : (Context) -> Response raise, summary? : String, description? : String, tags? : Array[String], deprecated? : Bool, operation_id? : String, status_code? : Int, responses? : Array[ResponseSpec], name? : String, endpoint? : Endpoint, security? : Array[SecurityRequirement], dependencies? : Array[String], include_in_schema? : Bool, validate? : Bool, openapi_extra? : Json) -> Unitfn App::include_router(self : App, router : Router, prefix? : String, tags? : Array[String], security? : Array[SecurityRequirement], dependencies? : Array[String], responses? : Array[ResponseSpec], deprecated? : Bool, include_in_schema? : Bool) -> Unitfn App::patch(self : App, path : String, handler : (Context) -> Response raise, summary? : String, description? : String, tags? : Array[String], deprecated? : Bool, operation_id? : String, status_code? : Int, responses? : Array[ResponseSpec], name? : String, endpoint? : Endpoint, security? : Array[SecurityRequirement], dependencies? : Array[String], include_in_schema? : Bool, validate? : Bool, openapi_extra? : Json) -> Unitfn App::post(self : App, path : String, handler : (Context) -> Response raise, summary? : String, description? : String, tags? : Array[String], deprecated? : Bool, operation_id? : String, status_code? : Int, responses? : Array[ResponseSpec], name? : String, endpoint? : Endpoint, security? : Array[SecurityRequirement], dependencies? : Array[String], include_in_schema? : Bool, validate? : Bool, openapi_extra? : Json) -> Unitfn App::put(self : App, path : String, handler : (Context) -> Response raise, summary? : String, description? : String, tags? : Array[String], deprecated? : Bool, operation_id? : String, status_code? : Int, responses? : Array[ResponseSpec], name? : String, endpoint? : Endpoint, security? : Array[SecurityRequirement], dependencies? : Array[String], include_in_schema? : Bool, validate? : Bool, openapi_extra? : Json) -> Unitfn App::route(self : App, verb : Method, path : String, handler : (Context) -> Response raise, summary? : String, description? : String, tags? : Array[String], deprecated? : Bool, operation_id? : String, status_code? : Int, responses? : Array[ResponseSpec], name? : String, endpoint? : Endpoint, security? : Array[SecurityRequirement], dependencies? : Array[String], include_in_schema? : Bool, validate? : Bool, openapi_extra? : Json) -> Unitfn App::route_bg(self : App, verb : Method, path : String, handler : (Context, BackgroundTasks) -> Response raise, summary? : String, description? : String, tags? : Array[String], deprecated? : Bool, operation_id? : String, status_code? : Int, responses? : Array[ResponseSpec], name? : String, endpoint? : Endpoint, security? : Array[SecurityRequirement], dependencies? : Array[String], include_in_schema? : Bool, validate? : Bool, openapi_extra? : Json) -> Unitfn App::route_stream(self : App, verb : Method, path : String, handler : (Context) -> StreamingResponse raise, summary? : String, description? : String, tags? : Array[String], deprecated? : Bool, operation_id? : String, status_code? : Int, responses? : Array[ResponseSpec], name? : String, endpoint? : Endpoint, security? : Array[SecurityRequirement], dependencies? : Array[String], include_in_schema? : Bool, validate? : Bool, openapi_extra? : Json) -> Unitfn App::secure_digest(self : App, name : String, digest : DigestAuth, description? : String, auto_error? : Bool) -> Unitfn App::secure_oauth2(self : App, name : String, bearer : OAuth2PasswordBearer, scopes? : Array[(String, String)], description? : String, auto_error? : Bool) -> Unitfn App::secure_oauth2_code(self : App, name : String, code : OAuth2CodeBearer, scopes? : Array[(String, String)], description? : String, auto_error? : Bool) -> Unitfn App::stream(self : App, path : String, handler : (Context) -> StreamingResponse raise, summary? : String, description? : String, tags? : Array[String], deprecated? : Bool, operation_id? : String, status_code? : Int, responses? : Array[ResponseSpec], name? : String, endpoint? : Endpoint, security? : Array[SecurityRequirement], dependencies? : Array[String], include_in_schema? : Bool, validate? : Bool, openapi_extra? : Json) -> Unittype Mountlet HTTP_100_CONTINUE : Intlet HTTP_101_SWITCHING_PROTOCOLS : Intlet HTTP_102_PROCESSING : Intlet HTTP_103_EARLY_HINTS : Intlet HTTP_201_CREATED : Intlet HTTP_202_ACCEPTED : Intlet HTTP_203_NON_AUTHORITATIVE_INFORMATION : Intlet HTTP_204_NO_CONTENT : Intlet HTTP_205_RESET_CONTENT : Intlet HTTP_206_PARTIAL_CONTENT : Intlet HTTP_207_MULTI_STATUS : Intlet HTTP_208_ALREADY_REPORTED : Intlet HTTP_226_IM_USED : Intlet HTTP_300_MULTIPLE_CHOICES : Intlet HTTP_301_MOVED_PERMANENTLY : Intlet HTTP_302_FOUND : Intlet HTTP_303_SEE_OTHER : Intlet HTTP_304_NOT_MODIFIED : Intlet HTTP_306_RESERVED : Intlet HTTP_307_TEMPORARY_REDIRECT : Intlet HTTP_308_PERMANENT_REDIRECT : Intlet HTTP_400_BAD_REQUEST : Intlet HTTP_401_UNAUTHORIZED : Intlet HTTP_403_FORBIDDEN : Intlet HTTP_405_METHOD_NOT_ALLOWED : Intlet HTTP_406_NOT_ACCEPTABLE : Intlet HTTP_407_PROXY_AUTHENTICATION_REQUIRED : Intlet HTTP_408_REQUEST_TIMEOUT : Intlet HTTP_409_CONFLICT : Intlet HTTP_411_LENGTH_REQUIRED : Intlet HTTP_412_PRECONDITION_FAILED : Intlet HTTP_413_REQUEST_ENTITY_TOO_LARGE : Intlet HTTP_414_REQUEST_URI_TOO_LONG : Intlet HTTP_415_UNSUPPORTED_MEDIA_TYPE : Intlet HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE : Intlet HTTP_417_EXPECTATION_FAILED : Intlet HTTP_418_IM_A_TEAPOT : Intlet HTTP_421_MISDIRECTED_REQUEST : Intlet HTTP_422_UNPROCESSABLE_ENTITY : Intlet HTTP_424_FAILED_DEPENDENCY : Intlet HTTP_425_TOO_EARLY : Intlet HTTP_426_UPGRADE_REQUIRED : Intlet HTTP_428_PRECONDITION_REQUIRED : Intlet HTTP_429_TOO_MANY_REQUESTS : Intlet HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE : Intlet HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS : Intlet HTTP_500_INTERNAL_SERVER_ERROR : Intlet HTTP_501_NOT_IMPLEMENTED : Intlet HTTP_502_BAD_GATEWAY : Intlet HTTP_503_SERVICE_UNAVAILABLE : Intlet HTTP_504_GATEWAY_TIMEOUT : Intlet HTTP_505_HTTP_VERSION_NOT_SUPPORTED : Intlet HTTP_506_VARIANT_ALSO_NEGOTIATES : Intlet HTTP_507_INSUFFICIENT_STORAGE : Intlet HTTP_508_LOOP_DETECTED : Intlet HTTP_511_NETWORK_AUTHENTICATION_REQUIRED : Intlet WS_1000_NORMAL_CLOSURE : Intlet WS_1001_GOING_AWAY : Intlet WS_1003_UNSUPPORTED_DATA : Intlet WS_1005_NO_STATUS_RCVD : Intlet WS_1006_ABNORMAL_CLOSURE : Intlet WS_1007_INVALID_FRAME_PAYLOAD_DATA : Intlet WS_1008_POLICY_VIOLATION : Intlet WS_1010_MANDATORY_EXT : Intlet WS_1011_INTERNAL_ERROR : Intlet WS_1013_TRY_AGAIN_LATER : Intlet WS_1014_BAD_GATEWAY : Intlet WS_1015_TLS_HANDSHAKE : Intfn check_constraints(value : Json, constraints : Array[Constraint], loc : Array[String], errs : Array[ValidationError]) -> Unitfn data_url(content : Bytes, media_type? : String) -> Stringfn file_response(content : Bytes, filename? : String, media_type? : String, status? : Int, inline? : Bool) -> Responsefn redoc_ui(spec_url? : String, title? : String) -> Stringfn swagger_ui(spec_url? : String, title? : String) -> Stringfn validate_schema(schema : Schema, value : Json, loc : Array[String], errs : Array[ValidationError]) -> UnitInstall
Download zipmoonapi — a typed web framework for MoonBit (\u2190 FastAPI): routing, typed extractors, descriptor-driven validation, multi-version OpenAPI/Swagger (2.0 / 3.0 / 3.1) with security schemes, dependency injection, OAuth2 password-bearer with scopes, per-operation security enforcement, multipart and urlencoded form extractors, response_model filtering, background tasks, sub-application mounting, a CORS/gzip/exception-handler middleware stack with per-status handlers, Server-Sent Events, and WebSocket routes, on the moonasgi SEAM.
Dependencies