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[(String, SecurityScheme)]
enforcers : Map[String, (Context, Array[String], Int64) -> Result[AuthenticatedUser, Response]]
status_handlers : Map[Int, (Context) -> Response]
mounts : Array[(String, App)]
startup_hooks : Array[() -> Unit raise]
shutdown_hooks : Array[() -> Unit raise]
info : ApiInfo
clock : () -> Int64
}fn App::enable_docs(self : App, openapi_url? : String?, docs_url? : String?, redoc_url? : String?, version? : OpenApiVersion) -> Unitfn App::route_bg(self : App, verb : Method, path : String, handler : (Context, BackgroundTasks) -> Response raise, summary? : String, tags? : Array[String], deprecated? : Bool, endpoint? : Endpoint?, security? : Array[SecurityRequirement], include_in_schema? : Bool, validate? : Bool) -> Unitfn App::secure_oauth2(self : App, name : String, bearer : OAuth2PasswordBearer, scopes? : Array[(String, String)]) -> 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
}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]
} derive(Eq)fn Field::new(name : String, schema : Schema, required? : Bool, description? : String, constraints? : Array[Constraint]) -> Fieldpub(all) struct HttpBasicCredentials {
username : String
password : String
}pub(all) struct License {
name : String
url : String
}pub(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 Routepub(all) enum SecurityScheme {
OAuth2Password(token_url~ : String, scopes~ : Array[(String, String)])
HttpBearer(bearer_format~ : String)
ApiKeyHeader(name~ : String)
ApiKeyQuery(name~ : String)
ApiKeyCookie(name~ : String)
HttpBasic
}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?) -> ServerSentEventpub(all) struct UploadFile {
name : String
filename : String
content_type : String
content : Bytes
} derive(Eq)fn 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 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)]) -> Responsefn 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