|
| 1 | +///| |
| 2 | +/// Pydantic-style field constraints and the scalar schema types, emitted into |
| 3 | +/// the OpenAPI schema and enforced off one descriptor. Every `Constraint` is |
| 4 | +/// exercised — `Minimum` / `Maximum`, `ExclusiveMinimum` / `ExclusiveMaximum`, |
| 5 | +/// `MultipleOf`, `MinLength` / `MaxLength`, `Pattern`, `MinItems` / `MaxItems` — |
| 6 | +/// alongside the scalar types `SInt` / `SFloat` / `SStr` / `SBool` / `SNull` / |
| 7 | +/// `SArray` and a closed `SEnum`. A conforming body validates; a low/short body |
| 8 | +/// and a high/long body each return a `422` naming every violation by its |
| 9 | +/// pydantic error type. |
| 10 | +/// |
| 11 | +/// moon run examples/05-constraints |
| 12 | +#coverage.skip |
| 13 | +fn main { |
| 14 | + let color = @moonapi.SEnum(@moonapi.SStr, [ |
| 15 | + "red".to_json(), |
| 16 | + "green".to_json(), |
| 17 | + "blue".to_json(), |
| 18 | + ]) |
| 19 | + let schema = @moonapi.Schema::object("Order", [ |
| 20 | + @moonapi.Field::new("qty", @moonapi.SInt, constraints=[ |
| 21 | + @moonapi.Minimum(1.0), |
| 22 | + @moonapi.Maximum(99.0), |
| 23 | + ]), |
| 24 | + @moonapi.Field::new("step", @moonapi.SInt, constraints=[ |
| 25 | + @moonapi.MultipleOf(5.0), |
| 26 | + ]), |
| 27 | + @moonapi.Field::new("ratio", @moonapi.SFloat, constraints=[ |
| 28 | + @moonapi.ExclusiveMinimum(0.0), |
| 29 | + @moonapi.ExclusiveMaximum(1.0), |
| 30 | + ]), |
| 31 | + @moonapi.Field::new("code", @moonapi.SStr, constraints=[ |
| 32 | + @moonapi.MinLength(3), |
| 33 | + @moonapi.MaxLength(8), |
| 34 | + @moonapi.Pattern("^[a-z]+$"), |
| 35 | + ]), |
| 36 | + @moonapi.Field::new("tags", @moonapi.Schema::array(@moonapi.SStr), constraints=[ |
| 37 | + @moonapi.MinItems(1), |
| 38 | + @moonapi.MaxItems(3), |
| 39 | + ]), |
| 40 | + @moonapi.Field::new("active", @moonapi.SBool), |
| 41 | + @moonapi.Field::new("color", color), |
| 42 | + // A field whose value must be JSON null. |
| 43 | + @moonapi.Field::new("note", @moonapi.SNull, required=false), |
| 44 | + ]) |
| 45 | + let ep = @moonapi.Endpoint::new(request_body=Some(schema)) |
| 46 | + let app = @moonapi.App::new() |
| 47 | + app.post( |
| 48 | + "/orders", |
| 49 | + ctx => { |
| 50 | + let errs = ep.validate(ctx) |
| 51 | + if errs.length() > 0 { |
| 52 | + @moonapi.unprocessable(errs) |
| 53 | + } else { |
| 54 | + @moonapi.text(201, "order accepted") |
| 55 | + } |
| 56 | + }, |
| 57 | + endpoint=Some(ep), |
| 58 | + ) |
| 59 | + let hit = (label : String, body : String) => { |
| 60 | + let req : @moonasgi.Request = { |
| 61 | + http_method: "POST", |
| 62 | + path: "/orders", |
| 63 | + query_string: b"", |
| 64 | + headers: [("content-type", "application/json")], |
| 65 | + body: @utf8.encode(body), |
| 66 | + } |
| 67 | + let r = app.handle(req) |
| 68 | + println("\{label} -> \{r.status} \{@utf8.decode_lossy(r.body[:])}") |
| 69 | + } |
| 70 | + // Within every bound. |
| 71 | + hit( |
| 72 | + "ok ", "{\"qty\":5,\"step\":10,\"ratio\":0.5,\"code\":\"abc\",\"tags\":[\"a\",\"b\"],\"active\":true,\"color\":\"red\"}", |
| 73 | + ) |
| 74 | + // Below the lower bounds, too short, wrong scalar types, off the enum. |
| 75 | + hit( |
| 76 | + "low ", "{\"qty\":0,\"step\":7,\"ratio\":1.5,\"code\":\"AB\",\"tags\":[],\"active\":\"yes\",\"color\":\"pink\",\"note\":5}", |
| 77 | + ) |
| 78 | + // Above the upper bounds, too long, too many items. |
| 79 | + hit( |
| 80 | + "high", "{\"qty\":100,\"step\":5,\"ratio\":0.0,\"code\":\"toolongcode\",\"tags\":[\"a\",\"b\",\"c\",\"d\"],\"active\":false,\"color\":\"green\"}", |
| 81 | + ) |
| 82 | + println("--- OpenAPI 3.1.0 (constraints + enum in the schema) ---") |
| 83 | + println(app.openapi_json(version=@moonapi.OpenApi31)) |
| 84 | +} |
0 commit comments