Pudu programming language
Menu
Package

@chrismichaelps / pudu-lang-docgen

Documentation publishing for Pudu: articles, API references, navigation, search, and static websites

0.1.0Apache-2.01

InstallClose

RestTest.pudu

Pudu149 lines7.3 KB

GitHub ↗
1/** @Docgen.Rest.Tests — interface descriptions become operation and schema references */2module PuduLangDocgen.RestTest34import Std.Io as Io5import Std.Json as Json6import Std.Result as Result7import Std.Test as Test8import PuduLangDocgen as Docgen9import PuduLangDocgen.Markdown.Phrase as Phrase10import PuduLangDocgen.Meta as Meta11import PuduLangDocgen.Rest.OpenApi as OpenApi12import PuduLangDocgen.Rest.Pages as Pages13import PuduLangDocgen.Yaml as Yaml1415/// A current-format description with shared parameters, references, and schemas.16fn current() -> Str {17  [18    "openapi: 3.0.3",19    "info:",20    "  title: Pet Store",21    "  version: 1.2.0",22    "  description: Manage **pets**.",23    "servers:",24    "  - url: https://pets.example/v1",25    "paths:",26    "  /pets/\{id\}:",27    "    parameters:",28    "      - $ref: '#/components/parameters/Id'",29    "    get:",30    "      operationId: getPet",31    "      summary: Get a pet",32    "      tags: [pets]",33    "      responses:",34    "        '200':",35    "          description: The pet",36    "          content:",37    "            application/json:",38    "              schema:",39    "                $ref: '#/components/schemas/Pet'",40    "        '404':",41    "          description: Not found",42    "    delete:",43    "      operationId: deletePet",44    "      deprecated: true",45    "      responses:",46    "        '204':",47    "          description: Deleted",48    "  /pets:",49    "    post:",50    "      operationId: addPet",51    "      tags: [pets]",52    "      requestBody:",53    "        required: true",54    "        content:",55    "          application/json:",56    "            schema:",57    "              type: array",58    "              items:",59    "                $ref: '#/components/schemas/Pet'",60    "      responses:",61    "        '201':",62    "          description: Created",63    "components:",64    "  parameters:",65    "    Id:",66    "      name: id",67    "      in: path",68    "      schema:",69    "        type: integer",70    "        format: int64",71    "  schemas:",72    "    Pet:",73    "      type: object",74    "      required: [name]",75    "      properties:",76    "        name:",77    "          type: string",78    "          description: Given name",79    "        kind:",80    "          $ref: '#/components/schemas/Kind'",81    "    Kind:",82    "      type: string",83    "      enum: [cat, dog]"84  ].join("\n")85}8687/// An older-format description with a body parameter and a host.88fn older() -> Str {89  "\{\"swagger\": \"2.0\", \"info\": \{\"title\": \"Legacy\"\}, \"host\": \"legacy.example\", \"basePath\": \"/api\", \"paths\": \{\"/items\": \{\"post\": \{\"parameters\": [\{\"name\": \"item\", \"in\": \"body\", \"required\": true, \"schema\": \{\"$ref\": \"#/definitions/Item\"\}\}], \"responses\": \{\"200\": \{\"description\": \"ok\", \"schema\": \{\"type\": \"string\"\}\}\}\}\}\}, \"definitions\": \{\"Item\": \{\"type\": \"object\"\}\}\}"90}9192/// A service read from YAML or JSON text, or an empty one.93fn service(text: Str) -> OpenApi.Service {94  let value = if text.startsWith("\{") {95    match Json.decode(text) {96      case Ok(found) => Meta.fromJson(&found)97      case Err(_) => Docgen.Nothing98    }99  } else { Result.unwrapOr(Yaml.decode(text), Docgen.Nothing) }100  match OpenApi.read(&value) {101    case Ok(found) => found102    case Err(_) => OpenApi.Service{title: "", version: "", description: "", servers: [], operations: [], schemas: []}103  }104}105106/// The failure message of a description that does not read.107fn refused(text: Str) -> Str {108  match OpenApi.read(&Result.unwrapOr(Yaml.decode(text), Docgen.Nothing)) {109    case Ok(_) => "accepted"110    case Err(reason) => reason111  }112}113114/// Runs every reading, description, page, and identity contract.115fn main() -> Int {116  let pets = service(current())117  let legacy = service(older())118  let context = Phrase.Scope{page: "rest/pets.html", source: "docs/rest/pets.yml", outputs: mapOf([]), references: mapOf([]), settings: Phrase.rendering()}119  let (page, references, problems) = Pages.page(&pets, "pets", &context)120  let checks = Test.suite("Rest", &[121      Test.equals("recognized", &[OpenApi.recognizes(&Result.unwrapOr(Yaml.decode(current()), Docgen.Nothing)), OpenApi.recognizes(&Docgen.Fields([("title", Docgen.Text("x"))]))], &[true, false]),122      Test.equals("title and version", &(pets.title, pets.version), &("Pet Store", "1.2.0")),123      Test.equals("servers", &[pets.servers, legacy.servers], &[["https://pets.example/v1"], ["https://legacy.example/api"]]),124      Test.equals("operations in path order", &pets.operations.map(|held: OpenApi.Operation| held.method + " " + held.id), &["GET getPet", "DELETE deletePet", "POST addPet"]),125      Test.equals("shared path parameter", &pets.operations[0].parameters, &[OpenApi.Parameter{name: "id", location: "path", required: true, kind: "integer (int64)", description: ""}]),126      Test.equals("response content", &pets.operations[0].responses[0].payloads, &[OpenApi.Payload{media: "application/json", kind: "Pet"}]),127      Test.equals("request body", &(pets.operations[2].body, pets.operations[2].bodyRequired), &([OpenApi.Payload{media: "application/json", kind: "array of Pet"}], true)),128      Test.equals("deprecated flag", &pets.operations[1].deprecated, &true),129      Test.equals("schemas sorted", &pets.schemas.map(|held: OpenApi.Schema| held.name), &["Kind", "Pet"]),130      Test.equals("schema properties", &pets.schemas[1].properties, &[OpenApi.Property{name: "name", kind: "string", required: true, description: "Given name"}, OpenApi.Property{name: "kind", kind: "Kind", required: false, description: ""}]),131      Test.equals("enumerations", &pets.schemas[0].values, &["cat", "dog"]),132      Test.equals("body parameter becomes a payload", &legacy.operations[0].body, &[OpenApi.Payload{media: "application/json", kind: "Item"}]),133      Test.equals("older response schema", &legacy.operations[0].responses[0].payloads[0].kind, &"string"),134      Test.equals("generated operation id", &legacy.operations[0].id, &"post /items"),135      Test.equals("title required", &refused("openapi: 3.0.0\ninfo:\n  version: 1"), &"interface description needs info.title"),136      Test.equals("describe combinations", &OpenApi.describe(&Docgen.Fields([("oneOf", Docgen.Items([Docgen.Fields([("type", Docgen.Text("string"))]), Docgen.Fields([("$ref", Docgen.Text("#/definitions/A"))])]))])), &"string or A"),137      Test.equals("references", &references.map(|held: Docgen.Reference| held.uid + " " + held.href), &["pets rest/pets.html", "pets.getPet rest/pets.html#getpet", "pets.addPet rest/pets.html#addpet", "pets.deletePet rest/pets.html#deletepet", "pets.schemas.Kind rest/pets.html#schema-kind", "pets.schemas.Pet rest/pets.html#schema-pet"]),138      Test.equals("no problems", &problems, &[]),139      Test.equals("outline groups by tag", &page.headings.filter(|held: Docgen.Heading| held.level == 2).map(|held: Docgen.Heading| held.title), &["Servers", "pets", "Operations", "Schemas"]),140      Test.that("method badge and path", page.body.contains("<span class=\"method method-get\">GET</span><code>/pets/\{id\}</code>")),141      Test.that("schema links", page.body.contains("<a class=\"xref\" href=\"#schema-pet\">Pet</a>")),142      Test.that("description rendered", page.body.contains("<strong>pets</strong>")),143      Test.that("deprecated badge", page.body.contains("<span class=\"badge deprecated\">Deprecated</span>"))144    ])145  let ran = Test.run(&checks)146  for failure in Test.failuresOf(&ran) { let _printed = Io.writeErrorLine(failure) }147  Test.report(&ran)148}149