moonapi — a typed web framework for MoonBit (← 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 self-built HS256 JWT and scopes, per-operation security enforcement, multipart/urlencoded form and file extractors, response_model filtering, background tasks, sub-application mounting, a CORS/gzip(real DEFLATE)/exception-handler middleware stack with per-status handlers, Server-Sent Events, and WebSocket routes, on the moonasgi SEAM.
Dependencies
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"]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:
let v31 = app.openapi_json(version=OpenApi31) // OpenAPI 3.1.0
let v30 = app.openapi_json(version=OpenApi30) // OpenAPI 3.0.3
let v20 = app.openapi_json(version=Swagger20) // Swagger 2.0
let docs_page = @moonapi.swagger_ui() // a Swagger UI page
// Serve it (native, via mooncat):
@mooncat.serve(app.to_asgi(), port=8000)pub suberror JwtError {
MalformedToken(String)
UnsupportedAlg(String)
BadSignature
Expired
NotYetValid
}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) -> Unitpub(all) enum Constraint {
Minimum(Double)
Maximum(Double)
ExclusiveMinimum(Double)
ExclusiveMaximum(Double)
MultipleOf(Double)
MinLength(Int)
MaxLength(Int)
Pattern(String)
MinItems(Int)
MaxItems(Int)
} derive(Eq)pub(all) struct Contact {
name : String
url : String
email : String
}type DeclaredSchemetype DepsScopepub struct DigestAuth {
realm : String
verify : (String) -> Bool
}pub(all) struct Ed25519PrivateKey {
seed : Bytes
}pub(all) struct Ed25519PublicKey {
key : Bytes
}pub(all) struct Field {
name : String
schema : Schema
required : Bool
description : String
constraints : Array[Constraint]
default : Json?
} derive(Eq)pub(all) struct HttpBasicCredentials {
username : String
password : String
}pub(all) struct License {
name : String
url : String
}type Mountpub struct OAuth2CodeBearer {
authorization_url : String
token_url : String
refresh_url : String
secret : String
}fn OAuth2CodeBearer::new(authorization_url : String, token_url : String, secret : String, refresh_url? : String) -> OAuth2CodeBearerfn OAuth2CodeBearer::scheme(self : OAuth2CodeBearer, scopes? : Array[(String, String)]) -> SecuritySchemepub(all) struct OAuth2PasswordBearer {
token_url : String
secret : String
}fn OAuth2PasswordBearer::authenticate(self : OAuth2PasswordBearer, ctx : Context, now_secs : Int64, scopes? : Array[String]) -> Result[AuthenticatedUser, Response]fn OAuth2PasswordBearer::scheme(self : OAuth2PasswordBearer, scopes? : Array[(String, String)]) -> SecuritySchemetype Routefn Router::delete(self : Router, 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 Router::get(self : Router, 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 Router::patch(self : Router, 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 Router::post(self : Router, 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 Router::put(self : Router, 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 Router::route(self : Router, 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 Router::route_bg(self : Router, 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 Router::stream(self : Router, 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) -> Unitpub(all) enum SecurityScheme {
OAuth2Password(token_url~ : String, scopes~ : Array[(String, String)])
OAuth2Code(authorization_url~ : String, token_url~ : String, refresh_url~ : String, scopes~ : Array[(String, String)])
HttpBearer(bearer_format~ : String)
ApiKeyHeader(name~ : String)
ApiKeyQuery(name~ : String)
ApiKeyCookie(name~ : String)
HttpBasic
HttpDigest
OpenIdConnect(url~ : String)
}pub(all) struct Server {
url : String
description : String
}pub(all) struct ServerSentEvent {
data : String
event : String?
id : String?
retry : Int?
comment : String?
}fn ServerSentEvent::new(data : String, event? : String?, id? : String?, retry? : Int?, comment? : String?) -> ServerSentEventlet 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 base64url_decode(s : String) -> Bytesfn base64url_encode(data : Bytes) -> Stringfn check_constraints(value : Json, constraints : Array[Constraint], loc : Array[String], errs : Array[ValidationError]) -> Unitfn constant_time_eq(a : Bytes, b : Bytes) -> Boolfn ed25519_public_from_seed(seed : Bytes) -> Bytesfn ed25519_sign(seed : Bytes, msg : Bytes) -> Bytesfn ed25519_verify(pub_key : Bytes, msg : Bytes, sig : Bytes) -> Boolfn file_response(content : Bytes, filename? : String, media_type? : String, status? : Int, inline? : Bool) -> Responsefn hmac_sha256(key : Bytes, msg : Bytes) -> Bytesfn inflate(data : Bytes) -> Bytesfn jwt_verify_eddsa(token : String, key : Ed25519PublicKey, now_secs : Int64) -> Map[String, Json] raise JwtErrorfn jwt_verify_es256(token : String, key : EcdsaPublicKey, now_secs : Int64) -> Map[String, Json] raise JwtErrorfn jwt_verify_rs256(token : String, key : RsaPublicKey, now_secs : Int64) -> Map[String, Json] raise JwtErrorfn redoc_ui(spec_url? : String, title? : String) -> Stringfn sha256(msg : Bytes) -> Bytesfn sha512(msg : Bytes) -> Bytesfn sse_response(events : Array[ServerSentEvent], status? : Int, headers? : Array[(String, String)]) -> StreamingResponsefn 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 (← 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 self-built HS256 JWT and scopes, per-operation security enforcement, multipart/urlencoded form and file extractors, response_model filtering, background tasks, sub-application mounting, a CORS/gzip(real DEFLATE)/exception-handler middleware stack with per-status handlers, Server-Sent Events, and WebSocket routes, on the moonasgi SEAM.
Dependencies