;;; note: source file ./src/openapi-generator/jso.scm
;;; newer than compiled /run/current-system/profile/lib/guile/3.0/site-ccache/openapi-generator/jso.go
;;; found fresh local cache at /tmp/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/guile-openapi-generator/trunk/src/openapi-generator/jso.scm.go
;;; note: source file ./src/openapi-generator/yaml.scm
;;; newer than compiled /run/current-system/profile/lib/guile/3.0/site-ccache/openapi-generator/yaml.go
;;; found fresh local cache at /tmp/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/guile-openapi-generator/trunk/src/openapi-generator/yaml.scm.go
;;; note: source file ./src/openapi-generator.scm
;;; newer than compiled /run/current-system/profile/lib/guile/3.0/site-ccache/openapi-generator.go
;;; found fresh local cache at /tmp/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/guile-openapi-generator/trunk/src/openapi-generator.scm.go
;;; note: source file ./src/openapi-generator/generator.scm
;;; newer than compiled /run/current-system/profile/lib/guile/3.0/site-ccache/openapi-generator/generator.go
;;; found fresh local cache at /tmp/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/guile-openapi-generator/trunk/src/openapi-generator/generator.scm.go
;;; note: source file ./src/openapi-generator/naming.scm
;;; newer than compiled /run/current-system/profile/lib/guile/3.0/site-ccache/openapi-generator/naming.go
;;; found fresh local cache at /tmp/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/guile-openapi-generator/trunk/src/openapi-generator/naming.scm.go
;;; note: source file ./src/openapi-generator/reader.scm
;;; newer than compiled /run/current-system/profile/lib/guile/3.0/site-ccache/openapi-generator/reader.go
;;; found fresh local cache at /tmp/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/guile-openapi-generator/trunk/src/openapi-generator/reader.scm.go
;;; note: source file ./src/openapi-generator/json.scm
;;; newer than compiled /run/current-system/profile/lib/guile/3.0/site-ccache/openapi-generator/json.go
;;; found fresh local cache at /tmp/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/guile-openapi-generator/trunk/src/openapi-generator/json.scm.go
;;; note: source file ./src/openapi-generator/bundle.scm
;;; newer than compiled /run/current-system/profile/lib/guile/3.0/site-ccache/openapi-generator/bundle.go
;;; found fresh local cache at /tmp/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/guile-openapi-generator/trunk/src/openapi-generator/bundle.scm.go
;;; note: source file ./src/openapi-generator/spec.scm
;;; newer than compiled /run/current-system/profile/lib/guile/3.0/site-ccache/openapi-generator/spec.go
;;; found fresh local cache at /tmp/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/guile-openapi-generator/trunk/src/openapi-generator/spec.scm.go
;;; note: source file ./src/openapi-generator/json-schema.scm
;;; newer than compiled /run/current-system/profile/lib/guile/3.0/site-ccache/openapi-generator/json-schema.go
;;; found fresh local cache at /tmp/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/guile-openapi-generator/trunk/src/openapi-generator/json-schema.scm.go
;;; note: source file ./src/openapi-generator/transforms.scm
;;; newer than compiled /run/current-system/profile/lib/guile/3.0/site-ccache/openapi-generator/transforms.go
;;; found fresh local cache at /tmp/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/guile-openapi-generator/trunk/src/openapi-generator/transforms.scm.go
;;; note: source file ./src/openapi-generator/schema.scm
;;; newer than compiled /run/current-system/profile/lib/guile/3.0/site-ccache/openapi-generator/schema.go
;;; found fresh local cache at /tmp/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/guile-openapi-generator/trunk/src/openapi-generator/schema.scm.go
;;; note: source file ./src/openapi-generator/config.scm
;;; newer than compiled /run/current-system/profile/lib/guile/3.0/site-ccache/openapi-generator/config.go
scanning directories for unit tests...
scanning present working directory for unit tests...
shuffled the files with seed 198432627 (replay with VERITAS_SEED=198432627)
found unit tests: (yaml-writer-spec.scm artanis-routes-spec.scm spec-spec.scm naming-spec.scm runtime-spec.scm reader-spec.scm jso-spec.scm client-spec.scm guile-routes-spec.scm directional-spec.scm real-specs-spec.scm yaml-spec.scm routes-spec.scm media-spec.scm schema-spec.scm integration-spec.scm models-spec.scm code-spec.scm controllers-spec.scm input-spec.scm generator-spec.scm)
running unit tests from file: yaml-writer-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 198432627 (replay with VERITAS_SEED=198432627)
+----------------------------------------------+
[📑 suite] Collections
[🧪 test] An empty document node is not a mapping
✅ got: {}
✅ got: []
[🧪 test] The empty collections have their own spelling
✅ got: object: {}
array: []
[🧪 test] A sequence is a block sequence
✅ got: tags:
- pets
- store
[🧪 test] A nested sequence is indented under its dash
✅ got: matrix:
-
- 1
- 2
[🧪 test] A sequence of mappings puts the dash before the first member
✅ got: parameters:
- name: petId
in: path
- name: limit
in: query
+----------------------------------------------+
[📑 suite] Scalars
[🧪 test] A status code key stays plain
[⚙️ assert] a key is read as key text, so 200 cannot become a number
✅ got: 200:
description: OK
[🧪 test] The empty string is quoted
✅ got: value: ""
[🧪 test] The other scalars have their own spelling
✅ got: a: true
b: false
c: null
✅ got: count: 42
ratio: 3.5
[🧪 test] A leading indicator is quoted
✅ got: value: "-x"
✅ got: value: "@home"
[🧪 test] A plain string stays plain
✅ got: name: Pet
[🧪 test] A colon or a hash that would end the scalar is quoted
✅ got: title: "Pet: the best"
✅ got: title: "one #two"
[⚙️ assert] a colon inside a URL does not need quotes
✅ got: url: https://example.com/v1
[🧪 test] A string that reads as a boolean or null is quoted
✅ got: value: "yes"
✅ got: value: "null"
✅ got: value: "off"
[🧪 test] A string that reads as a number is quoted
✅ got: code: "200"
✅ got: value: "-7"
✅ got: value: "0x1f"
[🧪 test] A version stays a string
[⚙️ assert] 3.0.3 would become a number if quoted wrongly
✅ got: openapi: 3.0.3
✅ got: openapi: 3.0.3
[🧪 test] A key that needs quotes gets them
✅ got: "a: b": 1
+----------------------------------------------+
[📑 suite] Multiline text
[🧪 test] Indentation inside the text survives
✅ got: first
indented
[🧪 test] Text that cannot be carried by a block is quoted
[⚙️ assert] a block would need an indentation indicator
✅ got: description: " leading space\nsecond"
[⚙️ assert] more than one trailing line break is spelled out
✅ got: description: "first\nsecond\n\n"
[🧪 test] Text with lines becomes a literal block
✅ got: description: |
first
second
[🧪 test] A text without a final newline strips the chomping
✅ got: description: |-
first
second
+----------------------------------------------+
[📑 suite] Files
[🧪 test] A document is written to a file and read back
✅ got: /tmp/guile-openapi-generator/yaml-writer-15801-1-1794339858/openapi.yaml
✅ got: ((openapi . 3.0.3) (paths))
+----------------------------------------------+
[📑 suite] Round trip
[🧪 test] A whole document reads back unchanged
✅ got: ((openapi . 3.0.3) (info (title . Pets: a store) (version . 1.0.0)) (flag . #t) (count . 42) (nothing . null) (empty-object) (empty-array . #()) (description . First line.
Second line.
) (paths (/pets/{petId} (get (tags . #(pets)) (parameters . #(((name . petId) (in . path) (required . #t) (schema (type . string))))) (responses (200 (description . OK)))))))
[🧪 test] Every ambiguous scalar reads back as a string
[⚙️ assert] "200" survives
✅ got: 200
[⚙️ assert] "-7" survives
✅ got: -7
[⚙️ assert] "0x1f" survives
✅ got: 0x1f
[⚙️ assert] "yes" survives
✅ got: yes
[⚙️ assert] "no" survives
✅ got: no
[⚙️ assert] "on" survives
✅ got: on
[⚙️ assert] "off" survives
✅ got: off
[⚙️ assert] "null" survives
✅ got: null
[⚙️ assert] "~" survives
✅ got: ~
[⚙️ assert] "3.0.3" survives
✅ got: 3.0.3
[⚙️ assert] "1e3" survives
✅ got: 1e3
[⚙️ assert] "2020-01-01" survives
✅ got: 2020-01-01
[⚙️ assert] "a: b" survives
✅ got: a: b
[⚙️ assert] "one #two" survives
✅ got: one #two
[⚙️ assert] "-x" survives
✅ got: -x
[⚙️ assert] "@home" survives
✅ got: @home
[⚙️ assert] "" survives
✅ got:
ℹ️ info - ran 5 tasks in: 0 seconds, 30021 microseconds
running unit tests from file: artanis-routes-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 198432627 (replay with VERITAS_SEED=198432627)
+----------------------------------------------+
[📑 suite] Options
[🧪 test] With auth is kept as an extension
✅ got: #t
[⚙️ assert] no security scheme is invented
✅ got: #f
[🧪 test] Produces with no schema still declares the media type
✅ got: (image/png)
[🧪 test] From body alone means JSON
✅ got: application/json
[🧪 test] Consumes may be written as a list
✅ got: application/xml
[🧪 test] Consumes makes a request body of that media type
✅ got: application/json
[⚙️ assert] the shape of the body is not guessed
✅ got: ()
[🧪 test] A route without options has no body
✅ got: #f
[🧪 test] Produces becomes the media type of the response
✅ got: image/png
✅ got: ()
+----------------------------------------------+
[📑 suite] Reading declarations
[🧪 test] A call that only looks like a route is ignored
✅ got: ()
[🧪 test] A declaration inside a procedure body is found
✅ got: getMetrics
[🧪 test] Every declaration is read, in order
✅ got: (get /pets get /pets/{petId} post /pets delete /pets/{petId} get /pets/{petId}/photo.png get /metrics)
+----------------------------------------------+
[📑 suite] Paths
[🧪 test] A placeholder becomes a required path parameter
✅ got: (petId)
✅ got: path
✅ got: #t
[🧪 test] An Artanis parameter becomes a placeholder
✅ got: /pets/{petId}
+----------------------------------------------+
[📑 suite] Specification
[🧪 test] The routes become a specification the reader accepts
✅ got: Artanis API
✅ got: 6
✅ got: (getPets postPets getPetsByPetId deletePetsByPetId getPetsByPetIdPhotoPng getMetrics)
ℹ️ info - ran 4 tasks in: 0 seconds, 30356 microseconds
running unit tests from file: spec-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 198432627 (replay with VERITAS_SEED=198432627)
+----------------------------------------------+
[📑 suite] Operations
[🧪 test] Path level parameters are inherited
✅ got: (animalId)
[⚙️ assert] a path parameter is required
✅ got: #t
[🧪 test] operationId wins, otherwise method and path decide
✅ got: list-pets
[⚙️ assert] post-pets has no operationId
✅ got: post-pets
[🧪 test] Operation level parameters win over path level ones
[⚙️ assert] the parameter is not duplicated
✅ got: 1
✅ got: from operation
[🧪 test] Every path and method becomes an operation
✅ got: (list-pets post-pets get-pet-by-id delete-pets-by-pet-id)
[🧪 test] Method, path, summary and tags are read
✅ got: post
✅ got: /animals
✅ got: Create an animal
✅ got: #f
+----------------------------------------------+
[📑 suite] Document
[🧪 test] The title, version and servers are read
✅ got: Petstore
✅ got: 3.0.3
✅ got: https://api.example.com/v1
✅ got: 2
[🧪 test] The component schemas are read in order
✅ got: (Pet NewPet)
[⚙️ assert] Pet is a component schema
✅ got: #t
+----------------------------------------------+
[📑 suite] References
[🧪 test] An unresolvable pointer raises
✅ success catching error: misc-error: (#f JSON pointer does not resolve ~S (#/components/schemas/Nope) #f)
[🧪 test] A local pointer resolves
✅ got: object
[🧪 test] A pointer escape is understood
[⚙️ assert] the escape sequence for a slash is understood
✅ got: string
+----------------------------------------------+
[📑 suite] Responses
[🧪 test] The success response is found
✅ got: 200
✅ got: application/json
[🧪 test] A default response is a fallback
✅ got: default
[🧪 test] A response reference is resolved
✅ got: Something went wrong
✅ got: 1
+----------------------------------------------+
[📑 suite] Servers
[🧪 test] An explicit variable value replaces a placeholder
✅ got: https://us.example.com/v2
[🧪 test] Server variables are expanded with their defaults
✅ got: https://eu.example.com/v1
+----------------------------------------------+
[📑 suite] Security
[🧪 test] Operation security wins over the global one
✅ got: (((oauth2 write:animals)))
[🧪 test] Global security applies to an operation without its own
✅ got: (((api_key)))
[🧪 test] The security schemes are read
✅ got: (api_key basic_auth bearer_auth oauth2 open_id)
✅ got: http
✅ got: bearer
✅ got: JWT
[🧪 test] An empty operation security means public
[⚙️ assert] deleteAnimal opts out
✅ got: ()
ℹ️ info - ran 6 tasks in: 0 seconds, 28143 microseconds
running unit tests from file: naming-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 198432627 (replay with VERITAS_SEED=198432627)
+----------------------------------------------+
[📑 suite] Case conversion
[🧪 test] kebab, snake and Pascal
✅ got: pet-photo-urls
✅ got: pet_photo_urls
✅ got: PetPhotoUrls
✅ got: petPhotoUrls
✅ got: PET_PHOTO_URLS
+----------------------------------------------+
[📑 suite] Splitting words
[🧪 test] camelCase and PascalCase split
✅ got: (pet photo urls)
✅ got: (list pets)
[🧪 test] Separators split too
✅ got: (pet id)
✅ got: (pet id)
✅ got: (pets pet id)
[🧪 test] A run of capitals is one word
[⚙️ assert] the last capital starts the next word
✅ got: (http response)
[🧪 test] Digits stay with their word
✅ got: (int64)
+----------------------------------------------+
[📑 suite] Safe identifiers
[🧪 test] A reserved word gains a suffix
✅ got: else-field
✅ got: quote-field
✅ got: lambda-field
[🧪 test] An empty name becomes something bindable
✅ got: unnamed
[🧪 test] An identifier never starts with a digit
✅ got: n-3d-model
[🧪 test] An ordinary name is only case-folded
✅ got: pet-photo-urls
+----------------------------------------------+
[📑 suite] Operations
[🧪 test] The operationId wins when it exists
✅ got: list-pets
[🧪 test] A path becomes a slug
✅ got: pets-by-pet-id-photos
✅ got: root
[🧪 test] Without an operationId, method and path decide
✅ got: get-pets-by-pet-id
✅ got: delete-pets-by-pet-id
+----------------------------------------------+
[📑 suite] Scheme entities
[🧪 test] Enumeration constants carry the value
✅ got: pet-status-in-progress
[🧪 test] A module name is a list of symbols
✅ got: (petstore controllers artanis)
[🧪 test] Records, predicates, constructors and accessors
✅ got: <pet>
✅ got: pet?
✅ got: make-pet
✅ got: pet-photo-urls
ℹ️ info - ran 5 tasks in: 0 seconds, 20914 microseconds
running unit tests from file: runtime-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 198432627 (replay with VERITAS_SEED=198432627)
+----------------------------------------------+
[📑 suite] HTTP values
[🧪 test] Responses are built with the status they mean
✅ got: 200
✅ got: 201
✅ got: 204
✅ got: #t
✅ got: 401
[🧪 test] Setting a header replaces it
[⚙️ assert] the name is stored as it was given
✅ got: ((ACCEPT . text/plain))
[🧪 test] Path parameters are substituted
✅ got: /pets/a%20b
[🧪 test] Query strings encode and decode
✅ got: a=1&c=x%20y
✅ got: ((a . 1) (c . x y) (d . /))
✅ got: ()
[🧪 test] A status family stands for every code in it
✅ got: (200 201 202 299)
✅ got: 200
[⚙️ assert] a 500 is outside the 2XX family
✅ success catching error: openapi-http-error: (500 ((status . 500) (message . boom)))
[🧪 test] A status that was not declared is an error
✅ got: 200
[⚙️ assert] 500 is not 200
✅ success catching error: openapi-http-error: (500 ((status . 500) (message . boom)))
[🧪 test] A request is a value
✅ got: 10
✅ got: application/json
[🧪 test] Form bodies spell numbers and booleans and coerce them back
✅ got: a=1&b=false&c=x%20y&d=true
✅ got: ((a . 1) (count . 3) (subscribe . #t))
✅ got: ((count . many))
✅ got: ((subscribe . #f))
[🧪 test] Headers are looked up without case
✅ got: application/json
✅ got: application/json
+----------------------------------------------+
[📑 suite] Validation
[🧪 test] valid? answers without raising
✅ got: #t
✅ got: #f
[⚙️ assert] an unrelated error is not swallowed
✅ success catching error: wrong-type-arg: (car Wrong type argument in position 1 (expecting pair): ~S (()) (()))
[🧪 test] Numeric ranges
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
[⚙️ assert] an inexact multiple is accepted within tolerance
✅ got: #t
[⚙️ assert] a whole inexact multiple is accepted
✅ got: #t
[⚙️ assert] an inexact non-multiple is still rejected
✅ got: #t
[⚙️ assert] an exact rational multiple is accepted
✅ got: #t
[🧪 test] A missing required value is reported with its path
✅ got: ((pet name) is required but is missing)
[🧪 test] A predicate is checked
✅ got: #t
✅ got: #t
[🧪 test] Collections
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: (l)
[🧪 test] String length and pattern
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
[🧪 test] Formats
✅ got: #t
✅ got: #f
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #f
✅ got: #t
[⚙️ assert] a format without a rule accepts anything
✅ got: #t
[🧪 test] The wrong type is reported
✅ got: (pet age)
✅ got: (pet age)
[🧪 test] Enumerations
✅ got: #t
✅ got: (s)
+----------------------------------------------+
[📑 suite] Authentication
[🧪 test] A granted scope satisfies the alternative
✅ got: ((Authorization . Bearer t))
[🧪 test] Credentials that satisfy nothing raise
[⚙️ assert] no credentials, secured operation
✅ success catching error: openapi-auth-error: (no credentials satisfy the security requirements of this operation)
[🧪 test] A public operation needs no credentials
✅ got: ()
✅ got: ()
[🧪 test] Basic authentication is base64 of user:password
✅ got: ((Authorization . Basic dTpw))
[🧪 test] A cookie API key becomes a Cookie header
✅ got: ((Cookie . session=s))
[🧪 test] An API key goes in the header it names
✅ got: ((X-API-Key . s))
✅ got: ()
[🧪 test] A query API key goes in the query
✅ got: ()
✅ got: ((api_key . s))
[🧪 test] The first satisfiable alternative wins
✅ got: ((Authorization . Bearer t))
[🧪 test] A missing scope makes an alternative unsatisfiable
[⚙️ assert] the token does not carry write
✅ success catching error: openapi-auth-error: (no credentials satisfy the security requirements of this operation)
[🧪 test] A bearer token is sent as a bearer token
✅ got: ((Authorization . Bearer t))
[🧪 test] Extra headers are always sent
✅ got: ((X-Trace . 1))
+----------------------------------------------+
[📑 suite] Query parameter serialization
[🧪 test] A deepObject parameter is read back as an alist
✅ got: ((status . open) (limit . 3))
[🧪 test] An exploded form array repeats the name
✅ got: ((tag . a) (tag . b))
[🧪 test] spaceDelimited and pipeDelimited use their own separator
✅ got: ((tag . a b))
✅ got: ((tag . a|b))
[🧪 test] A non-exploded form array is comma separated
✅ got: ((tag . a,b))
[🧪 test] An exploded array is read back as a list
✅ got: (a b)
[🧪 test] A non-exploded array is split and coerced
✅ got: (1 2)
[🧪 test] deepObject qualifies each member with the parameter name
✅ got: ((filter[status] . open))
[🧪 test] An absent optional value produces no entries
✅ got: ()
+----------------------------------------------+
[📑 suite] Cookie parameters
[🧪 test] Cookies are rendered as one header value
✅ got: a=1; b=2
[🧪 test] A quoted cookie value loses its quotes
✅ got: a b
[🧪 test] A cookie is read by name from the Cookie header
✅ got: abc
✅ got: dark
[🧪 test] A missing cookie is #f, not the whole header
✅ got: #f
[🧪 test] Two Cookie headers are folded into one
✅ got: ((Cookie . a=1; session=s) (Content-Type . application/json))
ℹ️ info - ran 5 tasks in: 0 seconds, 34110 microseconds
running unit tests from file: reader-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 198432627 (replay with VERITAS_SEED=198432627)
+----------------------------------------------+
[📑 suite] Validation
[🧪 test] An unknown major version is refused
✅ success catching error: misc-error: (#f ~A (<string> declares OpenAPI 4.0.0; only OpenAPI 3.x is supported) #f)
[🧪 test] A document without an openapi member is refused
✅ success catching error: misc-error: (#f ~A (<string> has no 'openapi' member; not an OpenAPI document) #f)
[🧪 test] 3.x is accepted
✅ got: #t
✅ got: #t
✅ got: #f
✅ got: #f
[🧪 test] A Swagger 2.0 document is refused with its version
[⚙️ assert] Swagger is not OpenAPI 3
✅ success catching error: misc-error: (#f ~A (test/fixtures/swagger2.yaml declares Swagger 2.0; only OpenAPI 3.x is supported) #f)
+----------------------------------------------+
[📑 suite] Format detection
[🧪 test] An unknown extension falls back to YAML
✅ got: yaml
[🧪 test] A file name decides
✅ got: json
✅ got: yaml
✅ got: yaml
[🧪 test] Content decides when the name does not
✅ got: json
✅ got: yaml
+----------------------------------------------+
[📑 suite] Parsing
[🧪 test] YAML and JSON versions of the same document agree
✅ got: 3.0.3
✅ got: object
✅ got: #(id label)
✅ got: #t
[🧪 test] read-spec accepts a path and a document
✅ got: Mini
✅ got: Mini
[🧪 test] A string is read the same way
✅ got: 3.0.3
+----------------------------------------------+
[📑 suite] Documents and fragments
[🧪 test] A document is parsed without being validated as OpenAPI
✅ got: Old
[🧪 test] A locator is a file or URL, not document text
✅ got: #t
✅ got: #f
[🧪 test] Fragments merge objects and replace values
✅ got: ((a . 1) (b . 2) (c . 3))
[⚙️ assert] objects merge recursively
✅ got: ((a (x . 1) (y . 2)))
[⚙️ assert] a scalar is replaced
✅ got: ((a . 3))
[🧪 test] A format is read from a URL path, not its query
✅ got: json
✅ got: yaml
ℹ️ info - ran 4 tasks in: 0 seconds, 27401 microseconds
running unit tests from file: jso-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 198432627 (replay with VERITAS_SEED=198432627)
+----------------------------------------------+
[📑 suite] Accessors
[🧪 test] jso-delete removes one member
✅ got: ((a . 1) (c 4))
[🧪 test] jso-ref-path walks nested objects
✅ got: 4
[⚙️ assert] a missing step yields #f
✅ got: #f
[🧪 test] Keys and values keep the document order
✅ got: (a b c)
✅ got: (1 2 ((d . 4)))
[🧪 test] jso-append lets later entries win
✅ got: ((a . 2) (b . 1))
[🧪 test] jso-put keeps the position of an existing member
✅ got: ((a . 9) (b . 2))
[🧪 test] A deep copy shares no pairs
✅ got: ((a . 1) (b . 2) (c (d . 4)))
[⚙️ assert] the nested object is a copy
✅ got: #f
+----------------------------------------------+
[📑 suite] JSON merge patch
[🧪 test] A member replaces the target member
[⚙️ assert] b is replaced, a is kept
✅ got: ((a . 1) (b . 3))
[🧪 test] A member that is absent from the patch is untouched
[⚙️ assert] b and c survive, only a changes
✅ got: ((a . 9) (b . 2) (c (d . 4)))
[🧪 test] Objects merge recursively
[⚙️ assert] nested members merge, they do not replace
✅ got: ((c (d . 5) (e . 6)))
[🧪 test] A nested null deletes a nested member
[⚙️ assert] d is removed from the nested object
✅ got: ((a . 1) (b . 2) (c))
[🧪 test] A null member deletes the target member
[⚙️ assert] b is removed
✅ got: ((a . 1))
[🧪 test] A patch that is not an object replaces the target
[⚙️ assert] RFC 7386: anything else replaces
✅ got: #(1 2)
+----------------------------------------------+
[📑 suite] Objects and arrays
[🧪 test] null is a value, not an absence
[⚙️ assert] the member is present
✅ got: #t
[⚙️ assert] and it holds null
✅ got: #t
[⚙️ assert] an absent member falls back to the default
✅ got: nothing
[🧪 test] An empty object is the empty list
[⚙️ assert] nothing is in it
✅ got: ()
[🧪 test] A parsed object has string keys
✅ got: ((name . Pet))
[🧪 test] Arrays are vectors
[⚙️ assert] a JSON array is a vector
✅ got: #t
+----------------------------------------------+
[📑 suite] Normalisation
[🧪 test] A jso object round-trips through JSON
✅ got: 1
✅ got: 4
✅ got: (a b c)
[🧪 test] Symbol keys become strings
✅ got: ((name . Pet))
[🧪 test] Normalisation descends into arrays
✅ got: #(((name . Pet)))
ℹ️ info - ran 4 tasks in: 0 seconds, 34020 microseconds
running unit tests from file: client-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 198432627 (replay with VERITAS_SEED=198432627)
+----------------------------------------------+
[📑 suite] Parameter serialization
[🧪 test] A deepObject query parameter is qualified by the runtime
✅ got: #t
✅ got: #t
[🧪 test] Both transports fold duplicate Cookie headers
✅ got: #t
✅ got: #t
[🧪 test] A cookie parameter becomes a Cookie header
✅ got: #t
✅ got: #t
[🧪 test] An array query parameter uses its declared style
✅ got: #t
+----------------------------------------------+
[📑 suite] Generated text
[🧪 test] The plain Guile client uses (web client)
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #f
[🧪 test] An operation authenticates before it sends
✅ got: #t
[⚙️ assert] the public operation asks for no credentials
✅ got: #t
[🧪 test] A client is a record with a base URL and credentials
✅ got: #t
✅ got: #t
[⚙️ assert] the first server of the specification is used
✅ got: #t
[🧪 test] Every operation is a procedure
[⚙️ assert] four operations, the constructor and the transport
✅ got: 6
✅ got: #t
✅ got: #t
✅ got: #t
[🧪 test] The libcurl client uses guile-curl
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #f
[🧪 test] The security schemes are a literal
✅ got: #t
✅ got: #t
✅ got: #t
+----------------------------------------------+
[📑 suite] Generated client against a server
[🧪 test] The client talks to the server: query, credentials, JSON out and back
✅ got: (200 (Rex Bella) Mia 201 Cadet 204)
[⚙️ assert] the server answered a GET /pets
✅ got: #t
[⚙️ assert] the server answered a POST /pets
✅ got: #t
✅ got: secret
✅ got: limit=10
✅ got: {"name":"Cadet"}
[🧪 test] An undeclared status raises instead of decoding
✅ got: (500)
ℹ️ info - ran 3 tasks in: 2 seconds, -128352 microseconds
running unit tests from file: guile-routes-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 198432627 (replay with VERITAS_SEED=198432627)
+----------------------------------------------+
[📑 suite] Specification
[🧪 test] The routes become a specification the reader accepts
✅ got: Guile API
✅ got: 7
✅ got: (listPets postPets getPetById deletePetsByPetId putPetsByPetId getHealth getHealthz)
+----------------------------------------------+
[📑 suite] Undoing a path pattern
[🧪 test] A pattern without captures keeps its path
✅ got: /pets
✅ got: /
[🧪 test] A capture on every segment is handled
✅ got: /pets/{petId}/photos/{photoId}
[🧪 test] A pattern that disagrees with its parameters is refused
✅ success catching error: misc-error: (#f The pattern has more capture groups than path parameters ~S ~S (^/pets/([^/]+)/?$ ()) #f)
✅ success catching error: misc-error: (#f The pattern has fewer capture groups than path parameters ~S ~S (^/pets/?$ (petId)) #f)
[🧪 test] A capture becomes a placeholder
✅ got: /pets/{petId}
[🧪 test] An escaped character is unescaped
✅ got: /v1.0/health
✅ got: /a+b
+----------------------------------------------+
[📑 suite] A route table
[🧪 test] The handler name becomes the operationId
✅ got: listPets
✅ got: getPetById
[🧪 test] A declaration outside a table is read too
✅ got: getHealthz
[🧪 test] A call that only looks like a route is ignored
✅ got: ()
[🧪 test] A path with no captures has no parameters
✅ got: ()
[🧪 test] Every route of the table is read, in order
✅ got: (get /pets post /pets get /pets/{petId} get /v1.0/health delete /pets/{petId} get /healthz put /pets/{petId})
[🧪 test] A quoted parameter list is read, strings or symbols
✅ got: (petId)
✅ got: (petId)
[🧪 test] A capture becomes a required path parameter
✅ got: (petId)
✅ got: path
✅ got: #t
ℹ️ info - ran 3 tasks in: 0 seconds, 31769 microseconds
running unit tests from file: directional-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 198432627 (replay with VERITAS_SEED=198432627)
+----------------------------------------------+
[📑 suite] Inherited discriminators
[🧪 test] A oneOf inherits the discriminator of its branches' parent
✅ got: (#t 2 #t 3)
+----------------------------------------------+
[📑 suite] Directional models
[🧪 test] A single model set keeps readOnly and writeOnly members optional
✅ got: (Ada #t)
[🧪 test] The client sends a request model and decodes a response model
✅ got: #t
✅ got: #t
✅ got: #t
[🧪 test] The Request and Response records load and round-trip
✅ got: (Ada 12345678 a1 #f #t #f #t)
[🧪 test] Request and Response sets differ in the right members
✅ got: #t
✅ got: #t
✅ got: #f
✅ got: #t
✅ got: #f
✅ got: #t
✅ got: #t
✅ got: #t
[🧪 test] The command line exposes the directional options
✅ got: 0
✅ got: #t
[🧪 test] Excluding writeOnly members leaves a response-shaped model
✅ got: #f
✅ got: #t
✅ got: #t
[🧪 test] Excluding readOnly members leaves a request-shaped model
✅ got: #f
✅ got: #f
✅ got: #t
✅ got: #t
[🧪 test] Inline request and response models are not duplicated
✅ got: 1
✅ got: 1
✅ got: #t
✅ got: #f
✅ got: #t
✅ got: #f
[🧪 test] An operation response does not overwrite a component response
✅ got: 1
✅ got: 1
+----------------------------------------------+
[📑 suite] Enumerations
[🧪 test] Without fault tolerance an unknown enum still fails validation
✅ got: ((pet status))
[🧪 test] An extensible enum is only an enum when asked
✅ got: #f
✅ got: #t
✅ got: #t
[🧪 test] Fault-tolerant enums map unknown values to unrecognized
✅ got: (unrecognized #t unrecognized #t)
ℹ️ info - ran 3 tasks in: 3 seconds, -122554 microseconds
running unit tests from file: real-specs-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 198432627 (replay with VERITAS_SEED=198432627)
+----------------------------------------------+
[📑 suite] Stripe
[🧪 test] The document parses into the operations it declares
✅ got: Stripe API (customers and charges subset)
✅ got: 9
✅ got: 13
[🧪 test] A form and a JSON body are both declared for one operation
✅ got: (application/json application/x-www-form-urlencoded)
[🧪 test] A deepObject query parameter keeps its style and explode
✅ got: deepObject
✅ got: #t
[🧪 test] The generated tree compiles, loads and routes
[⚙️ assert] one route per operation
✅ got: 9
[⚙️ assert] the dispatcher is a procedure
✅ got: #t
[⚙️ assert] the models module exports converters
✅ got: #t
[⚙️ assert] generation is warning-free
✅ got: #f
+----------------------------------------------+
[📑 suite] GitHub
[🧪 test] A path level parameter belongs to every operation of the path
✅ got: (owner repo issue_number)
[🧪 test] The document parses into the operations it declares
✅ got: GitHub REST API (issues subset)
✅ got: 5
✅ got: 8
[🧪 test] A response that refers to a component response is resolved
✅ got: #t
✅ got: Validation failed
✅ got: application/json
[🧪 test] The generated tree compiles, loads and routes
[⚙️ assert] one route per operation
✅ got: 5
[⚙️ assert] the dispatcher is a procedure
✅ got: #t
[⚙️ assert] the models module exports converters
✅ got: #t
[⚙️ assert] generation is warning-free
✅ got: #f
+----------------------------------------------+
[📑 suite] Kubernetes
[🧪 test] A dotted schema name is kept as it is written
✅ got: #t
[🧪 test] A body with no record behind it is decoded without a converter
[⚙️ assert] no converter is named that does not exist
✅ got: #f
[🧪 test] The generated tree compiles, loads and routes
[⚙️ assert] one route per operation
✅ got: 6
[⚙️ assert] the dispatcher is a procedure
✅ got: #t
[⚙️ assert] the models module exports converters
✅ got: #t
[⚙️ assert] generation is warning-free
✅ got: #f
[🧪 test] The document parses into the operations it declares
✅ got: Kubernetes API (pods subset)
✅ got: 6
✅ got: 14
[🧪 test] Three JSON media types are dispatched apart
[⚙️ assert] application/json-patch+json is tested by name
✅ got: #t
[⚙️ assert] application/merge-patch+json is tested by name
✅ got: #t
[⚙️ assert] application/strategic-merge-patch+json is tested by name
✅ got: #t
✅ got: 3
+----------------------------------------------+
[📑 suite] Reading back what was written
[🧪 test] A real document survives the YAML writer
[⚙️ assert] github.yaml round trips
✅ got: ((openapi . 3.0.3) (info (title . GitHub REST API (issues subset)) (description . List, read and create the issues of a repository.
The endpoints require a token; the responses carry the rate-limit headers
GitHub sends.
) (version . 2022-11-28) (license (name . MIT) (url . https://docs.github.com/en/site-policy/licensing))) (servers . #(((url . https://api.github.com) (description . The public instance)))) (security . #(((bearerAuth . #())))) (tags . #(((name . issues) (description . Repository issues)))) (paths (/repos/{owner}/{repo}/issues (parameters . #(((name . owner) (in . path) (required . #t) (description . The account owner of the repository.) (schema (type . string))) ((name . repo) (in . path) (required . #t) (description . The name of the repository.) (schema (type . string))))) (get (operationId . issues/list-for-repo) (summary . List repository issues) (description . List issues in a repository, most recently created first.) (tags . #(issues)) (parameters . #(((name . milestone) (in . query) (description . A milestone number, "*" for any, or "none".) (schema (type . string) (nullable . #t))) ((name . state) (in . query) (description . The issue state.) (schema (type . string) (enum . #(open closed all)) (default . open))) ((name . labels) (in . query) (description . A comma-separated list of label names.) (schema (type . string))) ((name . per_page) (in . query) (description . Results per page.) (schema (type . integer) (minimum . 1) (maximum . 100) (default . 30))) ((name . page) (in . query) (description . The page of results to fetch.) (schema (type . integer) (default . 1))) ((name . If-None-Match) (in . header) (description . An ETag from a previous response.) (schema (type . string))))) (responses (200 (description . A list of issues) (headers (Link (description . Pagination links.) (schema (type . string))) (X-RateLimit-Remaining (description . Requests left in the current window.) (schema (type . integer)))) (content (application/json (schema (type . array) (items ($ref . #/components/schemas/issue)))))) (304 (description . Not modified)) (404 ($ref . #/components/responses/notFound)) (401 ($ref . #/components/responses/unauthorized)))) (post (operationId . issues/create) (summary . Create an issue) (tags . #(issues)) (requestBody (required . #t) (content (application/json (schema ($ref . #/components/schemas/issue-create))))) (responses (201 (description . Created) (content (application/json (schema ($ref . #/components/schemas/issue))))) (410 (description . The repository has issues disabled)) (422 ($ref . #/components/responses/validationFailed))))) (/repos/{owner}/{repo}/issues/{issue_number} (parameters . #(((name . owner) (in . path) (required . #t) (schema (type . string))) ((name . repo) (in . path) (required . #t) (schema (type . string))) ((name . issue_number) (in . path) (required . #t) (description . The number of the issue.) (schema (type . integer))))) (get (operationId . issues/get) (summary . Get an issue) (tags . #(issues)) (responses (200 (description . The issue) (content (application/json (schema ($ref . #/components/schemas/issue))))) (301 (description . Moved permanently)) (404 ($ref . #/components/responses/notFound)))) (patch (operationId . issues/update) (summary . Update an issue) (tags . #(issues)) (requestBody (required . #t) (content (application/json (schema ($ref . #/components/schemas/issue-update))))) (responses (200 (description . The updated issue) (content (application/json (schema ($ref . #/components/schemas/issue))))) (422 ($ref . #/components/responses/validationFailed)))) (delete (operationId . issues/delete) (summary . Delete an issue) (description . Only users with push access can delete an issue.) (tags . #(issues)) (responses (204 (description . Deleted)) (404 ($ref . #/components/responses/notFound)))))) (components (securitySchemes (bearerAuth (type . http) (scheme . bearer) (bearerFormat . JWT)) (basicAuth (type . http) (scheme . basic))) (responses (notFound (description . Resource not found) (content (application/json (schema ($ref . #/components/schemas/basic-error))))) (unauthorized (description . Requires authentication) (content (application/json (schema ($ref . #/components/schemas/basic-error))))) (validationFailed (description . Validation failed) (content (application/json (schema ($ref . #/components/schemas/validation-error)))))) (schemas (basic-error (type . object) (required . #(message)) (properties (message (type . string)) (documentation_url (type . string)) (status (type . string)))) (validation-error (type . object) (required . #(message)) (properties (message (type . string)) (errors (type . array) (items (type . object) (properties (resource (type . string)) (field (type . string)) (code (type . string))))))) (user (type . object) (required . #(login id)) (properties (login (type . string)) (id (type . integer) (readOnly . #t)) (avatar_url (type . string) (format . uri)) (type (type . string) (enum . #(User Organization Bot)) (default . User)))) (label (type . object) (required . #(name)) (properties (id (type . integer) (readOnly . #t)) (name (type . string)) (color (type . string)) (default (type . boolean) (default . #f)))) (milestone (type . object) (nullable . #t) (properties (number (type . integer)) (title (type . string)) (due_on (type . string) (format . date-time) (nullable . #t)))) (issue (type . object) (required . #(id number title state)) (properties (id (type . integer) (readOnly . #t)) (number (type . integer) (readOnly . #t)) (title (type . string)) (body (type . string) (nullable . #t)) (state (type . string) (enum . #(open closed))) (state_reason (type . string) (nullable . #t) (x-extensible-enum . #(completed not_planned reopened))) (user ($ref . #/components/schemas/user)) (labels (type . array) (items ($ref . #/components/schemas/label))) (assignee (allOf . #((($ref . #/components/schemas/user)))) (nullable . #t)) (milestone (allOf . #((($ref . #/components/schemas/milestone)))) (nullable . #t)) (comments (type . integer) (default . 0)) (reactions (type . object) (additionalProperties (type . integer))) (created_at (type . string) (format . date-time) (readOnly . #t)) (updated_at (type . string) (format . date-time) (readOnly . #t)) (closed_at (type . string) (format . date-time) (nullable . #t)))) (issue-create (type . object) (required . #(title)) (properties (title (type . string)) (body (type . string)) (assignee (type . string) (nullable . #t)) (milestone (type . integer) (nullable . #t)) (labels (type . array) (items (type . string))))) (issue-update (type . object) (properties (title (type . string)) (body (type . string) (nullable . #t)) (state (type . string) (enum . #(open closed))) (state_reason (type . string) (enum . #(completed not_planned reopened)) (nullable . #t)) (labels (type . array) (items (type . string))) (assignee (type . string) (nullable . #t)))))))
[⚙️ assert] stripe.yaml round trips
✅ got: ((openapi . 3.0.3) (info (title . Stripe API (customers and charges subset)) (version . 2024-06-20) (description . A subset of the Stripe API, enough to exercise the parts that make it
awkward to describe: every write is form-encoded, pagination is by cursor,
and every response is wrapped in an object envelope.
) (license (name . Apache-2.0))) (servers . #(((url . https://api.stripe.com) (description . Live mode)) ((url . https://api.stripe.com/v1) (description . The versioned prefix)))) (security . #(((bearerAuth . #())))) (tags . #(((name . customers)) ((name . charges)))) (paths (/v1/customers (get (operationId . ListCustomers) (summary . List all customers) (tags . #(customers)) (parameters . #(((name . limit) (in . query) (description . A limit on the number of objects returned.) (schema (type . integer) (minimum . 1) (maximum . 100) (default . 10))) ((name . starting_after) (in . query) (description . A cursor for use in pagination.) (schema (type . string))) ((name . ending_before) (in . query) (schema (type . string))) ((name . email) (in . query) (description . A case-sensitive filter on the customer email.) (schema (type . string))) ((name . expand) (in . query) (description . The paths to expand in the response.) (style . form) (explode . #f) (schema (type . array) (items (type . string)))) ((name . Stripe-Version) (in . header) (description . The API version to use for this request.) (schema (type . string))))) (responses (200 (description . A dictionary with a data property that contains the customers.) (content (application/json (schema ($ref . #/components/schemas/customer-list))))) (400 ($ref . #/components/responses/apiError)) (401 ($ref . #/components/responses/apiError)))) (post (operationId . CreateCustomer) (summary . Create a customer) (tags . #(customers)) (parameters . #(((name . Idempotency-Key) (in . header) (required . #t) (description . A unique key that makes the request safe to retry.) (schema (type . string))) ((name . Stripe-Version) (in . header) (schema (type . string))))) (requestBody (required . #t) (content (application/x-www-form-urlencoded (schema ($ref . #/components/schemas/customer-create))))) (responses (200 (description . The new customer) (content (application/json (schema ($ref . #/components/schemas/customer))))) (400 ($ref . #/components/responses/apiError)) (402 ($ref . #/components/responses/apiError))))) (/v1/customers/{customer} (parameters . #(((name . customer) (in . path) (required . #t) (description . The customer identifier, `cus_...`.) (schema (type . string))))) (get (operationId . RetrieveCustomer) (summary . Retrieve a customer) (tags . #(customers)) (parameters . #(((name . expand) (in . query) (style . form) (explode . #f) (schema (type . array) (items (type . string)))))) (responses (200 (description . The customer) (content (application/json (schema ($ref . #/components/schemas/customer))))) (404 ($ref . #/components/responses/apiError)))) (post (operationId . UpdateCustomer) (summary . Update a customer) (tags . #(customers)) (parameters . #(((name . Idempotency-Key) (in . header) (schema (type . string))))) (requestBody (required . #t) (content (application/x-www-form-urlencoded (schema ($ref . #/components/schemas/customer-update))))) (responses (200 (description . The updated customer) (content (application/json (schema ($ref . #/components/schemas/customer))))) (404 ($ref . #/components/responses/apiError)))) (delete (operationId . DeleteCustomer) (summary . Delete a customer) (tags . #(customers)) (responses (200 (description . The deleted customer) (content (application/json (schema ($ref . #/components/schemas/deleted-customer))))) (404 ($ref . #/components/responses/apiError))))) (/v1/charges (get (operationId . ListCharges) (summary . List all charges) (tags . #(charges)) (parameters . #(((name . limit) (in . query) (schema (type . integer) (minimum . 1) (maximum . 100) (default . 10))) ((name . customer) (in . query) (description . Only return charges for this customer.) (schema (type . string))) ((name . created) (in . query) (description . A filter on the creation date.) (style . deepObject) (explode . #t) (schema (type . object) (properties (gt (type . integer)) (gte (type . integer)) (lt (type . integer)) (lte (type . integer))))))) (responses (200 (description . A dictionary with a data property that contains the charges.) (content (application/json (schema ($ref . #/components/schemas/charge-list))))))) (post (operationId . CreateCharge) (summary . Create a charge) (description . Charges a card or a source. The amount is in the smallest currency
unit, and the currency is a three-letter ISO code.
) (tags . #(charges)) (parameters . #(((name . Idempotency-Key) (in . header) (required . #t) (schema (type . string))))) (requestBody (required . #t) (content (application/x-www-form-urlencoded (schema ($ref . #/components/schemas/charge-create))) (application/json (schema ($ref . #/components/schemas/charge-create))))) (responses (200 (description . The new charge) (content (application/json (schema ($ref . #/components/schemas/charge))))) (400 ($ref . #/components/responses/apiError)) (402 (description . The card was declined) (content (application/json (schema ($ref . #/components/schemas/api-errors))))) (409 ($ref . #/components/responses/apiError))))) (/v1/charges/{charge}/refunds (parameters . #(((name . charge) (in . path) (required . #t) (description . The charge identifier, `ch_...`.) (schema (type . string))))) (get (operationId . ListRefunds) (summary . List the refunds of a charge) (tags . #(charges)) (responses (200 (description . A list of refunds) (content (application/json (schema ($ref . #/components/schemas/refund-list))))) (404 ($ref . #/components/responses/apiError)))) (post (operationId . CreateRefund) (summary . Create a refund) (tags . #(charges)) (parameters . #(((name . Idempotency-Key) (in . header) (required . #t) (schema (type . string))))) (requestBody (required . #t) (content (application/x-www-form-urlencoded (schema (type . object) (required . #(charge)) (properties (charge (type . string)) (amount (type . integer) (description . A positive integer in the smallest currency unit.)) (reason (type . string) (enum . #(duplicate fraudulent requested_by_customer))) (metadata (type . object) (additionalProperties (type . string)))))))) (responses (200 (description . The new refund) (content (application/json (schema ($ref . #/components/schemas/refund))))) (400 ($ref . #/components/responses/apiError)) (409 (description . The charge is already refunded) (content (application/json (schema ($ref . #/components/schemas/api-errors))))))))) (components (securitySchemes (bearerAuth (type . http) (scheme . bearer) (description . The secret key, as a bearer token.))) (responses (apiError (description . The request failed) (content (application/json (schema ($ref . #/components/schemas/api-errors)))))) (schemas (api-errors (type . object) (required . #(error)) (properties (error (type . object) (required . #(type message)) (properties (type (type . string) (enum . #(api_error card_error idempotency_error invalid_request_error))) (charge (type . string)) (code (type . string) (nullable . #t)) (decline_code (type . string)) (doc_url (type . string) (format . uri)) (message (type . string)) (param (type . string)) (payment_intent (type . string)))))) (metadata (type . object) (additionalProperties (type . string)) (description . A set of key-value pairs.)) (address (type . object) (nullable . #t) (properties (city (type . string) (nullable . #t)) (country (type . string) (nullable . #t)) (line1 (type . string) (nullable . #t)) (line2 (type . string) (nullable . #t)) (postal_code (type . string) (nullable . #t)) (state (type . string) (nullable . #t)))) (customer (type . object) (required . #(id object)) (properties (id (type . string) (readOnly . #t)) (object (type . string) (readOnly . #t) (enum . #(customer))) (address ($ref . #/components/schemas/address)) (balance (type . integer) (description . The current balance, negative for credit.)) (created (type . integer) (format . unix-time) (readOnly . #t)) (currency (type . string) (nullable . #t)) (default_source (type . string) (nullable . #t)) (delinquent (type . boolean) (nullable . #t)) (description (type . string) (nullable . #t)) (email (type . string) (format . email) (nullable . #t)) (invoice_prefix (type . string)) (livemode (type . boolean) (readOnly . #t)) (metadata ($ref . #/components/schemas/metadata)) (name (type . string) (nullable . #t)) (phone (type . string) (nullable . #t)))) (customer-create (type . object) (properties (address ($ref . #/components/schemas/address)) (description (type . string)) (email (type . string)) (metadata ($ref . #/components/schemas/metadata)) (name (type . string)) (payment_method (type . string)) (phone (type . string)))) (customer-update (type . object) (properties (address ($ref . #/components/schemas/address)) (description (type . string)) (email (type . string)) (metadata ($ref . #/components/schemas/metadata)) (name (type . string)) (phone (type . string)))) (deleted-customer (type . object) (required . #(id deleted)) (properties (id (type . string)) (object (type . string)) (deleted (type . boolean)))) (customer-list (type . object) (required . #(object data has_more)) (properties (object (type . string) (enum . #(list))) (data (type . array) (items ($ref . #/components/schemas/customer))) (has_more (type . boolean)) (total_count (type . integer)) (url (type . string)))) (charge (type . object) (required . #(id object amount currency)) (properties (id (type . string) (readOnly . #t)) (object (type . string) (enum . #(charge))) (amount (type . integer)) (amount_captured (type . integer)) (captured (type . boolean)) (created (type . integer) (readOnly . #t)) (currency (type . string) (description . Three-letter ISO currency code, lower case.)) (customer (description . Either the customer identifier or the expanded object.) (anyOf . #(((type . string)) (($ref . #/components/schemas/customer)))) (nullable . #t)) (description (type . string) (nullable . #t)) (dispute (type . boolean) (nullable . #t)) (metadata ($ref . #/components/schemas/metadata)) (paid (type . boolean)) (refunded (type . boolean)) (status (type . string) (enum . #(succeeded pending failed))))) (charge-create (type . object) (required . #(amount currency)) (properties (amount (type . integer)) (currency (type . string)) (customer (type . string)) (description (type . string)) (capture (type . boolean) (default . #t)) (metadata ($ref . #/components/schemas/metadata)) (source (type . string) (description . A payment source identifier.)))) (charge-list (type . object) (required . #(object data has_more)) (properties (object (type . string)) (data (type . array) (items ($ref . #/components/schemas/charge))) (has_more (type . boolean)) (url (type . string)))) (refund (type . object) (required . #(id amount status)) (properties (id (type . string) (readOnly . #t)) (amount (type . integer)) (charge (type . string)) (created (type . integer) (readOnly . #t)) (currency (type . string)) (metadata ($ref . #/components/schemas/metadata)) (reason (type . string) (nullable . #t) (enum . #(duplicate fraudulent requested_by_customer))) (status (type . string) (enum . #(pending succeeded failed canceled))))) (refund-list (type . object) (properties (object (type . string)) (data (type . array) (items ($ref . #/components/schemas/refund))) (has_more (type . boolean)))))))
[⚙️ assert] kubernetes.yaml round trips
✅ got: ((openapi . 3.0.3) (info (title . Kubernetes API (pods subset)) (version . v1.30.0) (description . The core pods API. Responses are JSON by default; YAML is served when the
client asks for it.
) (license (name . Apache-2.0))) (servers . #(((url . https://kubernetes.default.svc) (description . The in-cluster API server)))) (security . #(((bearerAuth . #())))) (tags . #(((name . CoreV1)))) (paths (/api/v1/namespaces/{namespace}/pods (parameters . #(((name . namespace) (in . path) (required . #t) (description . Object name and auth scope, such as for teams and projects.) (schema (type . string))))) (get (operationId . listCoreV1NamespacedPod) (summary . List or watch objects of kind Pod) (tags . #(CoreV1)) (parameters . #(((name . pretty) (in . query) (description . If true, the output is pretty printed.) (schema (type . string))) ((name . allowWatchBookmarks) (in . query) (schema (type . boolean))) ((name . continue) (in . query) (description . The continue option should be set when retrieving more results.) (schema (type . string))) ((name . fieldSelector) (in . query) (schema (type . string))) ((name . labelSelector) (in . query) (schema (type . string))) ((name . limit) (in . query) (schema (type . integer) (format . int64))) ((name . resourceVersion) (in . query) (schema (type . string))) ((name . timeoutSeconds) (in . query) (schema (type . integer) (format . int64))) ((name . watch) (in . query) (description . Watch for changes to the described resources.) (schema (type . boolean))))) (responses (200 (description . OK) (content (application/json (schema ($ref . #/components/schemas/io.k8s.api.core.v1.PodList))) (application/yaml (schema (type . string))))) (401 (description . Unauthorized) (content (application/json (schema ($ref . #/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status))))))) (post (operationId . createCoreV1NamespacedPod) (summary . Create a Pod) (tags . #(CoreV1)) (parameters . #(((name . dryRun) (in . query) (description . If set, the operation is only validated.) (schema (type . string) (enum . #(All)))) ((name . fieldManager) (in . query) (description . The name of the manager making the change.) (schema (type . string))) ((name . fieldValidation) (in . query) (schema (type . string) (enum . #(Ignore Warn Strict)))))) (requestBody (required . #t) (content (application/json (schema ($ref . #/components/schemas/io.k8s.api.core.v1.Pod))))) (responses (201 (description . Created) (content (application/json (schema ($ref . #/components/schemas/io.k8s.api.core.v1.Pod))))) (401 (description . Unauthorized) (content (application/json (schema ($ref . #/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status))))) (409 (description . Conflict) (content (application/json (schema ($ref . #/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status)))))))) (/api/v1/namespaces/{namespace}/pods/{name} (parameters . #(((name . name) (in . path) (required . #t) (description . The name of the Pod.) (schema (type . string))) ((name . namespace) (in . path) (required . #t) (schema (type . string))))) (get (operationId . readCoreV1NamespacedPod) (summary . Read the specified Pod) (tags . #(CoreV1)) (parameters . #(((name . pretty) (in . query) (schema (type . string))) ((name . exact) (in . query) (description . Should the export be exact.) (schema (type . boolean))))) (responses (200 (description . OK) (content (application/json (schema ($ref . #/components/schemas/io.k8s.api.core.v1.Pod))))) (404 (description . Not found) (content (application/json (schema ($ref . #/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status))))))) (put (operationId . replaceCoreV1NamespacedPod) (summary . Replace the specified Pod) (tags . #(CoreV1)) (parameters . #(((name . dryRun) (in . query) (schema (type . string))) ((name . fieldManager) (in . query) (schema (type . string))))) (requestBody (required . #t) (content (application/json (schema ($ref . #/components/schemas/io.k8s.api.core.v1.Pod))))) (responses (200 (description . OK) (content (application/json (schema ($ref . #/components/schemas/io.k8s.api.core.v1.Pod))))) (409 (description . Conflict) (content (application/json (schema ($ref . #/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status))))))) (patch (operationId . patchCoreV1NamespacedPod) (summary . Partially update the specified Pod) (tags . #(CoreV1)) (parameters . #(((name . dryRun) (in . query) (schema (type . string))) ((name . fieldManager) (in . query) (schema (type . string))) ((name . force) (in . query) (schema (type . boolean))))) (requestBody (required . #t) (content (application/json-patch+json (schema (type . array) (items (type . object) (additionalProperties . #t)))) (application/merge-patch+json (schema ($ref . #/components/schemas/io.k8s.api.core.v1.Pod))) (application/strategic-merge-patch+json (schema ($ref . #/components/schemas/io.k8s.api.core.v1.Pod))))) (responses (200 (description . OK) (content (application/json (schema ($ref . #/components/schemas/io.k8s.api.core.v1.Pod))))) (409 (description . Conflict) (content (application/json (schema ($ref . #/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status))))))) (delete (operationId . deleteCoreV1NamespacedPod) (summary . Delete a Pod) (tags . #(CoreV1)) (parameters . #(((name . gracePeriodSeconds) (in . query) (schema (type . integer) (format . int64))) ((name . orphanDependents) (in . query) (schema (type . boolean))) ((name . propagationPolicy) (in . query) (schema (type . string) (enum . #(Orphan Background Foreground)))))) (responses (200 (description . OK) (content (application/json (schema ($ref . #/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status))))) (404 (description . Not found) (content (application/json (schema ($ref . #/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status))))))))) (components (securitySchemes (bearerAuth (type . http) (scheme . bearer))) (schemas (io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta (type . object) (properties (annotations (type . object) (additionalProperties (type . string))) (creationTimestamp (type . string) (format . date-time) (nullable . #t)) (deletionGracePeriodSeconds (type . integer) (format . int64) (nullable . #t)) (deletionTimestamp (type . string) (format . date-time) (nullable . #t)) (finalizers (type . array) (items (type . string))) (generateName (type . string)) (generation (type . integer) (format . int64)) (labels (type . object) (additionalProperties (type . string))) (name (type . string)) (namespace (type . string)) (resourceVersion (type . string) (readOnly . #t)) (uid (type . string) (readOnly . #t)))) (io.k8s.apimachinery.pkg.util.intstr.IntOrString (description . A value that can be an integer or a string.) (oneOf . #(((type . integer) (format . int32)) ((type . string))))) (io.k8s.apimachinery.pkg.api.resource.Quantity (description . A fixed-point number of a resource.) (type . string) (pattern . ^([+-]?[0-9.]+)([eEinumkKMGTP]*[-+]?[0-9]*)$)) (io.k8s.api.core.v1.ResourceRequirements (type . object) (properties (limits (type . object) (additionalProperties ($ref . #/components/schemas/io.k8s.apimachinery.pkg.api.resource.Quantity))) (requests (type . object) (additionalProperties ($ref . #/components/schemas/io.k8s.apimachinery.pkg.api.resource.Quantity))))) (io.k8s.api.core.v1.ContainerPort (type . object) (required . #(containerPort)) (properties (containerPort (type . integer) (format . int32)) (hostIP (type . string)) (hostPort (type . integer) (format . int32)) (name (type . string)) (protocol (type . string) (default . TCP) (enum . #(TCP UDP SCTP))))) (io.k8s.api.core.v1.Container (type . object) (required . #(name)) (properties (args (type . array) (items (type . string))) (command (type . array) (items (type . string))) (image (type . string)) (imagePullPolicy (type . string) (enum . #(Always IfNotPresent Never))) (name (type . string)) (ports (type . array) (items ($ref . #/components/schemas/io.k8s.api.core.v1.ContainerPort))) (resources ($ref . #/components/schemas/io.k8s.api.core.v1.ResourceRequirements)) (terminationMessagePath (type . string)) (workingDir (type . string)))) (io.k8s.api.core.v1.Toleration (type . object) (properties (effect (type . string)) (key (type . string)) (operator (type . string)) (tolerationSeconds (type . integer) (format . int64)) (value (type . string)))) (io.k8s.api.core.v1.PodSpec (type . object) (required . #(containers)) (properties (activeDeadlineSeconds (type . integer) (format . int64)) (containers (type . array) (items ($ref . #/components/schemas/io.k8s.api.core.v1.Container))) (dnsPolicy (type . string)) (hostNetwork (type . boolean)) (nodeName (type . string)) (nodeSelector (type . object) (additionalProperties (type . string))) (restartPolicy (type . string) (default . Always) (enum . #(Always OnFailure Never))) (serviceAccountName (type . string)) (terminationGracePeriodSeconds (type . integer) (format . int64)) (tolerations (type . array) (items ($ref . #/components/schemas/io.k8s.api.core.v1.Toleration))))) (io.k8s.api.core.v1.PodCondition (type . object) (required . #(status type)) (properties (lastProbeTime (type . string) (format . date-time)) (message (type . string)) (reason (type . string)) (status (type . string)) (type (type . string) (enum . #(PodScheduled Ready ContainersReady))))) (io.k8s.api.core.v1.PodStatus (type . object) (properties (conditions (type . array) (items ($ref . #/components/schemas/io.k8s.api.core.v1.PodCondition))) (hostIP (type . string)) (message (type . string)) (phase (type . string) (enum . #(Pending Running Succeeded Failed Unknown))) (podIP (type . string)) (qosClass (type . string)) (startTime (type . string) (format . date-time)))) (io.k8s.api.core.v1.Pod (description . A pod is a group of one or more containers.) (type . object) (properties (apiVersion (type . string)) (kind (type . string)) (metadata ($ref . #/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta)) (spec ($ref . #/components/schemas/io.k8s.api.core.v1.PodSpec)) (status ($ref . #/components/schemas/io.k8s.api.core.v1.PodStatus))) (x-kubernetes-group-version-kind . #(((group . ) (version . v1) (kind . Pod))))) (io.k8s.api.core.v1.PodList (type . object) (required . #(items)) (properties (apiVersion (type . string)) (items (type . array) (items ($ref . #/components/schemas/io.k8s.api.core.v1.Pod))) (kind (type . string)) (metadata (allOf . #((($ref . #/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta))))))) (io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails (type . object) (properties (causes (type . array) (items (type . object) (properties (field (type . string)) (message (type . string)) (reason (type . string))))) (group (type . string)) (kind (type . string)) (name (type . string)) (retryAfterSeconds (type . integer) (format . int32)) (uid (type . string)))) (io.k8s.apimachinery.pkg.apis.meta.v1.Status (description . A status is a more specific error or success message.) (type . object) (properties (apiVersion (type . string)) (code (type . integer) (format . int32)) (details ($ref . #/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails)) (kind (type . string)) (message (type . string)) (metadata ($ref . #/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta)) (reason (type . string)) (status (type . string) (enum . #(Success Failure))))))))
[🧪 test] The controllers of a real API read back as the same document
[⚙️ assert] github.yaml keeps its operations
✅ got: (delete /repos/{owner}/{repo}/issues/{issue_number} get /repos/{owner}/{repo}/issues get /repos/{owner}/{repo}/issues/{issue_number} patch /repos/{owner}/{repo}/issues/{issue_number} post /repos/{owner}/{repo}/issues)
[⚙️ assert] stripe.yaml keeps its operations
✅ got: (delete /v1/customers/{customer} get /v1/charges get /v1/charges/{charge}/refunds get /v1/customers get /v1/customers/{customer} post /v1/charges post /v1/charges/{charge}/refunds post /v1/customers post /v1/customers/{customer})
[⚙️ assert] kubernetes.yaml keeps its operations
✅ got: (delete /api/v1/namespaces/{namespace}/pods/{name} get /api/v1/namespaces/{namespace}/pods get /api/v1/namespaces/{namespace}/pods/{name} patch /api/v1/namespaces/{namespace}/pods/{name} post /api/v1/namespaces/{namespace}/pods put /api/v1/namespaces/{namespace}/pods/{name})
+----------------------------------------------+
[📑 suite] Content negotiation
[🧪 test] Each declared content type reaches its handler as its own shape
✅ got: (json-patch list=#t items=2 merge pod=#t policy=Never merge pod=#t policy=OnFailure)
ℹ️ info - ran 5 tasks in: 21 seconds, -172198 microseconds
running unit tests from file: yaml-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 198432627 (replay with VERITAS_SEED=198432627)
+----------------------------------------------+
[📑 suite] Block scalars
[🧪 test] The indentation of the block is removed
[⚙️ assert] relative indentation survives
✅ got: a
b
[🧪 test] A folded block joins its lines with spaces
[⚙️ assert] the strip chomping removes the trailing newline
✅ got: one two three
four
[🧪 test] A literal block keeps its line breaks
[⚙️ assert] the clip chomping adds one newline
✅ got: first line
second line
+----------------------------------------------+
[📑 suite] Scalars
[🧪 test] A plain scalar may continue at the same indentation
✅ got: first line second line
[🧪 test] Version-like scalars stay strings
[⚙️ assert] 3.0.3 is not a number
✅ got: 3.0.3
[🧪 test] A key with no value is null
[⚙️ assert] empty_value is json-null
✅ got: #t
[🧪 test] Quoted scalars keep their content
[⚙️ assert] double quoted escapes
✅ got: a: colon, "quote" and
newline
[⚙️ assert] a doubled quote is an escaped quote
✅ got: it's fine
[🧪 test] Numbers are numbers
✅ got: 42
✅ got: -7
✅ got: 3.5
✅ got: 1000.0
✅ got: 31
[🧪 test] A quoted scalar may continue on the next line
✅ got: first line second line
✅ got: first line second line
[🧪 test] A comment is not part of the value
[⚙️ assert] a # starts a comment only after a space
✅ got: ((a . 1) (b . x#not-a-comment))
[🧪 test] Booleans are booleans
✅ got: #t
✅ got: #f
+----------------------------------------------+
[📑 suite] Collections
[🧪 test] A sequence may sit at the indentation of its key
[⚙️ assert] - may be at the same column as k
✅ got: ((k . #(a b)))
[🧪 test] Deeply nested mappings are read
✅ got: ((a (b (c . 1))))
[🧪 test] A sequence of mappings is read whole
[⚙️ assert] two items
✅ got: 2
✅ got: second
✅ got: #(3 4)
[🧪 test] A flow sequence is a vector
[⚙️ assert] every element has the right type
✅ got: #(1 2.5 #t null text)
[🧪 test] A flow mapping nests
✅ got: ((a . 1) (b . #(2 3)) (c (d . 4)))
+----------------------------------------------+
[📑 suite] Documents
[🧪 test] A tab cannot indent a line
[⚙️ assert] a tab raises instead of guessing
✅ success catching error: misc-error: (#f YAML: a tab cannot be used for indentation ~S ( b: 1) #f)
[🧪 test] An empty document is null
[⚙️ assert] nothing is null
✅ got: #t
[🧪 test] Document markers and directives are ignored
✅ got: ((a . 1))
[🧪 test] The petstore fixture reads
✅ got: 3.0.3
[⚙️ assert] components is an object
✅ got: #t
+----------------------------------------------+
[📑 suite] Anchors
[🧪 test] An unknown alias is an error
[⚙️ assert] *nope has no anchor
✅ success catching error: misc-error: (#f YAML: unknown alias ~S (nope) #f)
[🧪 test] An alias repeats the anchored value
[⚙️ assert] copy equals base
✅ got: ((shared . #t) (count . 2))
[🧪 test] A merge key overlays an anchored mapping
[⚙️ assert] the merged member is present
✅ got: #t
[⚙️ assert] the explicit member wins
✅ got: 3
ℹ️ info - ran 5 tasks in: 0 seconds, 35358 microseconds
running unit tests from file: routes-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 198432627 (replay with VERITAS_SEED=198432627)
+----------------------------------------------+
[📑 suite] Parameters and bodies
[🧪 test] A location has to be one of the four
✅ got: header
✅ success catching error: misc-error: (#f A parameter lives in path, query, header or cookie ~S (body) #f)
[🧪 test] A request body has a media type and a schema
✅ got: #t
✅ got: application/json
✅ got: #/components/schemas/Pet
✅ got: text/plain
[⚙️ assert] a body of unknown shape is an empty schema
✅ got: ()
[🧪 test] A type or a schema becomes the schema
✅ got: integer
✅ got: #/components/schemas/Pet
[🧪 test] A parameter without a schema is a string
✅ got: string
✅ got: query
[🧪 test] A response with no schema has no content
✅ got: #f
✅ got: integer
[🧪 test] A response without a description gets the reason phrase
✅ got: Created
✅ got: OK
✅ got: Response
+----------------------------------------------+
[📑 suite] Routes
[🧪 test] A declared path parameter is kept where it was written
✅ got: (limit petId)
✅ got: integer
[⚙️ assert] a path parameter is required even when it was not said so
✅ got: #t
[🧪 test] A method may be spelled either way
✅ got: get
✅ got: delete
[🧪 test] Tags are text whatever they were written as
✅ got: (pets store)
[🧪 test] A placeholder becomes a required path parameter
✅ got: (petId photoId)
✅ got: (path path)
✅ got: (#t #t)
✅ got: string
[🧪 test] An unsupported method is refused
✅ success catching error: misc-error: (#f A route needs one of the HTTP methods ~S (fetch) #f)
[🧪 test] A declared operationId wins
✅ got: getPet
[🧪 test] A path is normalised
✅ got: /pets
✅ got: /pets
✅ got: /pets/{petId}
✅ got: /
[🧪 test] A missing operationId is derived from the method and the path
✅ got: getPetsByPetId
✅ got: getPets
+----------------------------------------------+
[📑 suite] Command line
[🧪 test] A source that does not exist is an error
✅ got: 1
[🧪 test] A file with no routes is an error
✅ got: 1
✅ got: No routes were found
[🧪 test] The usage lists the spec command
✅ got: 0
✅ got: openapi-generator spec
✅ got: --source
[🧪 test] The source reader defaults to Guile
✅ got: 0
[🧪 test] A route table becomes a file
✅ got: 0
✅ got: specification:
✅ got: Guile API
✅ got: 2.1.0
✅ got: 2
✅ got: https://b.example.com
[🧪 test] An Artanis source is read with --source artanis
✅ got: 0
✅ got: 6
[🧪 test] Both commands report their version
✅ got: 0
✅ got: 0
✅ got: guile-openapi-generator
✅ got: guile-openapi-generator
[🧪 test] An unknown source is an error
✅ got: 1
✅ got: Unknown route source
[🧪 test] The input is required
✅ got: 1
✅ got: --input
[🧪 test] The document can go to standard output
✅ got: 0
✅ got: openapi: 3.0.3
✅ got: /pets/{petId}
+----------------------------------------------+
[📑 suite] Document
[🧪 test] Security is written when it is given
✅ got: bearerAuth
✅ got: bearer
[🧪 test] An operation declares its parameters, body and responses
✅ got: getPet
✅ got: petId
✅ got: (200 404)
✅ got: #f
[🧪 test] The document has the OpenAPI members and the serviced paths
✅ got: 3.0.3
✅ got: Pet store
✅ got: https://api.example.com/v1
✅ got: (/pets /pets/{petId})
[⚙️ assert] the schemas are carried into components
✅ got: string
[🧪 test] A request body becomes a content object
✅ got: #t
✅ got: application/json
[🧪 test] Two methods on one path share the path item
✅ got: (get post)
[🧪 test] A route with no response still answers
✅ got: (200)
+----------------------------------------------+
[📑 suite] Mistakes
[🧪 test] An extension that is not one is refused
✅ success catching error: misc-error: (#f An OpenAPI extension key starts with x- ~S (artanis) #f)
[🧪 test] Two routes with one operationId are refused
✅ success catching error: misc-error: (#f Two routes share an operationId ~S (same) #f)
[🧪 test] Two routes with one method and path are refused
✅ success catching error: misc-error: (#f Two routes share an operationId ~S (getPets) #f)
[🧪 test] A server has to be a URL or an object
✅ success catching error: misc-error: (#f A server is a URL string or an object ~S (42) #f)
+----------------------------------------------+
[📑 suite] YAML
[🧪 test] A specification is written to a file
✅ got: /tmp/guile-openapi-generator/routes-15801-8-1794339884/openapi.yaml
✅ got: Pet store
[🧪 test] The generated document is read back by the reader
✅ got: Pet store
✅ got: (get-pets post-pets get-pet)
✅ got: (Pet)
✅ got: (petId)
ℹ️ info - ran 6 tasks in: 0 seconds, 83952 microseconds
running unit tests from file: media-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 198432627 (replay with VERITAS_SEED=198432627)
+----------------------------------------------+
[📑 suite] A dispatcher across media types
[🧪 test] Text and form bodies are decoded by media type
✅ got: (200 {"name":"Ada","count":3} (application/json) 200 bytes (text/plain (charset . utf-8)) hello Ada 3 #t T data hello json)
+----------------------------------------------+
[📑 suite] Transport helpers
[🧪 test] Text and binary responses set their content type
✅ got: #t
✅ got: #t
[🧪 test] A required body is enforced
✅ got: value
✅ got: 200
[🧪 test] A multipart body has a boundary and a part per value
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
[🧪 test] A response header is read without case
✅ got: v
+----------------------------------------------+
[📑 suite] A client across media types
[🧪 test] Text, bytes, forms, multipart and headers travel end to end
✅ got: (hello hello 4 4 Ada multipart 42 abc form plain 200)
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
[🧪 test] The libcurl transport sends bytes, not a latin-1 round-trip
✅ got: (hello hello 4 4 Ada multipart 42 abc form plain 200)
✅ got: #vu8(0 1 2 255)
ℹ️ info - ran 3 tasks in: 4 seconds, 213745 microseconds
running unit tests from file: schema-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 198432627 (replay with VERITAS_SEED=198432627)
+----------------------------------------------+
[📑 suite] Shapes
[🧪 test] An enumeration is an enumeration
✅ got: #t
✅ got: (fresh salt)
[🧪 test] A named scalar keeps its constraints
✅ got: #t
✅ got: email
✅ got: ^[^@]+@[^@]+\.[^@]+$
[🧪 test] A const is a one-value enumeration
✅ got: #t
✅ got: (accepted)
✅ got: string
[🧪 test] A nullable member is nullable
✅ got: #t
[🧪 test] A component array is a list of scalars
✅ got: #t
✅ got: string
[🧪 test] An object of only additionalProperties is a map
✅ got: #t
✅ got: #t
[🧪 test] A string with a format is a scalar with that format
✅ got: #t
✅ got: date-time
+----------------------------------------------+
[📑 suite] Registry
[🧪 test] Component schemas keep their names
✅ got: Animal
[🧪 test] Uses of a reference are not given a second name
[⚙️ assert] a $ref body is not a new record
✅ got: #f
[🧪 test] An array of references is a list of records
[⚙️ assert] it is a list
✅ got: #t
[⚙️ assert] of records
✅ got: #t
✅ got: Animal
[🧪 test] A reference becomes a record shape
[⚙️ assert] it is a record reference
✅ got: #t
✅ got: Owner
+----------------------------------------------+
[📑 suite] Composition
[🧪 test] allOf is flattened into one object
✅ got: #t
✅ got: (kind name born age weight nickname tags owner extra indoor lives)
[⚙️ assert] the child's required member is required
✅ got: #t
[🧪 test] The parent's required members stay required
✅ got: #t
[🧪 test] oneOf keeps its branches and discriminator
✅ got: #t
✅ got: kind
✅ got: 3
✅ got: ((cat . Cat) (dog . Dog) (fish . Fish))
+----------------------------------------------+
[📑 suite] Constraints
[🧪 test] Array constraints are read
✅ got: 1
✅ got: #t
[🧪 test] String constraints are read
✅ got: 1
✅ got: 40
[🧪 test] Numeric constraints are read
✅ got: 0
✅ got: 200
ℹ️ info - ran 4 tasks in: 0 seconds, 28340 microseconds
running unit tests from file: integration-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 198432627 (replay with VERITAS_SEED=198432627)
+----------------------------------------------+
[📑 suite] Generated client from a whole file
[🧪 test] An exact response keeps its exact check
✅ got: #t
✅ got: #t
✅ got: #t
[🧪 test] A 2XX response is checked against its family
[⚙️ assert] the search operation accepts every 2xx
✅ got: #t
[🧪 test] An unset optional header is left out, not sent as #f
✅ got: #t
+----------------------------------------------+
[📑 suite] A whole specification is read
[🧪 test] Every operation and schema is understood
✅ got: 5
✅ got: #t
+----------------------------------------------+
[📑 suite] Generated models from a whole file
[🧪 test] A map is checked against its declared size
✅ got: #t
✅ got: #t
[🧪 test] An inexact multipleOf is generated as declared
✅ got: #t
✅ got: #t
[🧪 test] A bare object becomes a map, not an empty record
✅ got: #t
✅ got: #f
[🧪 test] uniqueItems is only checked when it is declared
✅ got: 1
✅ got: #t
✅ got: #f
[🧪 test] A record that keeps undeclared members exports their accessor
✅ got: #t
✅ got: #t
+----------------------------------------------+
[📑 suite] The whole tree, used
[🧪 test] Records keep their data and the route table dispatches
✅ got: (web ((lang . en) (note . x)) ((source . web) (lang . en) (note . x)) web ((lang . en) (note . x)) (scheme scheme) ((width . 10) (unit . cm)) 12.34 (book ratings) must be a multiple of 0.01 (book counters) ((a . 1) (b . 2)) (book counters) ((a . 1) (b . two)) ((x . y)) ((title . rough) (pages . 3) (notes . later)) rough ((pages . 3) (notes . later)) Structure and Interpretation ((width . 10) (unit . cm)) web ((lang . en) (note . x)) 200 ((limit . 5) (x-request-id . #f)) ((book-id . 3f2504e0-4f89-11d3-9a0c-0305e82c3301)) Compilers 200)
[🧪 test] The generated client talks to a server
✅ got: (200 (SICP) SICP 299 (SICP) 201 Compilers 500)
✅ got: secret
✅ got: limit=5
✅ got: #t
✅ got: secret
✅ got: {"title":"Compilers","price":9.99}
ℹ️ info - ran 4 tasks in: 4 seconds, -7429 microseconds
running unit tests from file: models-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 198432627 (replay with VERITAS_SEED=198432627)
+----------------------------------------------+
[📑 suite] Generated text
[🧪 test] An object of only additionalProperties is a map
✅ got: #t
✅ got: #t
[🧪 test] An absent optional member is left out
✅ got: #t
[🧪 test] Composition is flattened into one record
✅ got: #t
✅ got: #t
✅ got: #t
[🧪 test] A union is a predicate and a decoder that uses the discriminator
✅ got: #t
✅ got: #t
[🧪 test] The converters are generated and named after the schema
✅ got: #t
✅ got: #t
✅ got: #t
[🧪 test] The docstring says where the file came from
✅ got: #t
✅ got: #t
[🧪 test] A record is a record, a constructor and a predicate
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
[🧪 test] A nullable member is written as null
✅ got: #t
✅ got: #t
✅ got: #t
[🧪 test] An enumeration becomes values and constants
✅ got: #t
✅ got: #t
✅ got: #t
[🧪 test] The constructor validates what the specification declares
✅ got: #t
✅ got: #t
✅ got: #t
+----------------------------------------------+
[📑 suite] Generated records
[🧪 test] A record is constructed, read and validated
✅ got: (cat Mia #t 9 #t #t)
[🧪 test] A record serialises to jso and back
✅ got: (Mia #t #t #f Mia Mia)
[🧪 test] A required member that accepts null may be #f
✅ got: (null null #f #f w)
[🧪 test] A discriminated union decodes into the right record
✅ got: (#t Mia #t #t #t #f (Animal))
[🧪 test] An enumeration is values, constants and a predicate
✅ got: (fresh #t (fish water))
[🧪 test] Named scalars, lists and maps validate through their predicates
✅ got: (#t #f #t #f #(a b) #t ())
[🧪 test] A merge patch replaces and removes members
✅ got: (Tom #f)
[🧪 test] A missing required member raises with the path of the field
✅ got: (((cat name) is required but is missing))
[🧪 test] A value outside the constraints raises
✅ got: ((dog name) Rex)
ℹ️ info - ran 2 tasks in: 27 seconds, 705671 microseconds
running unit tests from file: code-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 198432627 (replay with VERITAS_SEED=198432627)
+----------------------------------------------+
[📑 suite] Documents
[🧪 test] A docstring keeps its lines
✅ got: ((define (f value) "Check VALUE. Returns #t." value))
+----------------------------------------------+
[📑 suite] One line
[🧪 test] Quoted data prints with a quote
✅ got: '()
[⚙️ assert] an association keeps its dot
✅ got: '(("a" . "b"))
[🧪 test] A keyword prints as a keyword
✅ got: (f #:key value)
[🧪 test] An empty list is the empty list
✅ got: (list)
[🧪 test] Strings are literals, quotes and all
✅ got: (display "a \"quoted\" word")
[🧪 test] A call that fits keeps its arguments together
✅ got: (list 1 2 3)
[🧪 test] A form that fits stays on one line
✅ got: (list 1 2 3)
+----------------------------------------------+
[📑 suite] Broken forms
[🧪 test] A keyword argument starts a line and keeps its value
[⚙️ assert] the keyword and its value share a line
✅ got: #t
[🧪 test] A long call wraps its arguments under the first one
[⚙️ assert] the call starts the first line
✅ got: #t
[⚙️ assert] no line passes the width
✅ got: #t
[🧪 test] A let lines up its bindings
✅ got: ((let ((path (params->path "/pets" '())) (response (client-send client 'GET path))) (values path response)))
[🧪 test] A record type names its constructor, predicate and fields
✅ got: ((define-record-type <pet> (%make-pet id name tags) pet? (id pet-id) (name pet-name) (tags pet-tags)))
[🧪 test] A module header puts every clause on its own line
✅ got: ((define-module (petstore models) #:use-module (srfi srfi-1) #:export (pet? make-pet)))
[🧪 test] A long argument list puts one argument per line
✅ got: (define* (make-pet #:key
[⚙️ assert] an argument sits on a line of its own
✅ got: id
[⚙️ assert] the signature is closed after its last argument
✅ got: #f)))
[🧪 test] A definition indents its body
✅ got: ((define (pet-name value) (jso-ref (pet->jso value) "name")))
[⚙️ assert] a definition always breaks
✅ got: ((define empty '()))
[🧪 test] A cond puts one clause per line
✅ got: ((cond ((eq? kind 'cat) (jso->cat value)) (else (jso->dog value))))
[🧪 test] An if lines its branches up under the test
✅ got: ((if (eq? value #f) '() (list value)))
+----------------------------------------------+
[📑 suite] Data
[🧪 test] A comment belongs to the form below it
✅ got: ((list 1) ;; Why b is 2 (list 2))
[🧪 test] A long literal stacks its elements under the quote
✅ got: '(("type" . "apiKeyHeader")
✅ got: ("description" . "a long one"))
[🧪 test] Consecutive forms are separated by a blank line
✅ got: ((list 1) (list 2))
ℹ️ info - ran 4 tasks in: 0 seconds, 18202 microseconds
running unit tests from file: controllers-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 198432627 (replay with VERITAS_SEED=198432627)
+----------------------------------------------+
[📑 suite] Parameter serialization
[🧪 test] A deepObject query parameter is decoded back into an alist
✅ got: #t
[🧪 test] An array query parameter is decoded with its style
✅ got: #t
[🧪 test] A cookie parameter is read out of the Cookie header
✅ got: #t
+----------------------------------------------+
[📑 suite] Generated text
[🧪 test] An operation decodes its parameters
✅ got: #t
✅ got: #t
[🧪 test] The Artanis adapter registers one route per operation
✅ got: 4
✅ got: #t
✅ got: #t
[⚙️ assert] each route is a handler
✅ got: #t
[⚙️ assert] responses go through Artanis
✅ got: #t
[⚙️ assert] the concrete path is rebuilt from the parameters
✅ got: #t
[🧪 test] The portable controller exports a run-server adapter
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
[🧪 test] A path parameter becomes a capturing group
[⚙️ assert] the name of the captured parameter is kept
✅ got: #t
[🧪 test] The route table holds one route per operation
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: 4
[🧪 test] Handlers are looked up, not guessed
✅ got: #t
✅ got: #t
[🧪 test] A request body is decoded into the record
[⚙️ assert] the body is parsed as JSON
✅ got: #t
[⚙️ assert] the parsed node becomes the record
✅ got: #t
+----------------------------------------------+
[📑 suite] Generated dispatch
[🧪 test] Handlers receive the decoded parameters and responses are built
✅ got: (200 ((limit . 10)) ((pet-id . 42)) Bella list-pets (GET /nope))
ℹ️ info - ran 3 tasks in: 1 seconds, 213123 microseconds
running unit tests from file: input-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 198432627 (replay with VERITAS_SEED=198432627)
+----------------------------------------------+
[📑 suite] API fragments
[🧪 test] The command line accepts a fragment
✅ got: 0
✅ got: #t
[🧪 test] A fragment adds paths and schemas to the primary document
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
+----------------------------------------------+
[📑 suite] JSON Schema input
[🧪 test] A JSON Schema becomes a component and its definitions
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
[🧪 test] The command line accepts --json-schema-file
✅ got: 0
✅ got: #t
[🧪 test] Draft-04 bounds and const are understood
✅ got: #t
✅ got: #t
✅ got: #t
[🧪 test] Input and JSON Schema are mutually exclusive
✅ got: 1
[🧪 test] The root name and a nested pointer are honoured
✅ got: #t
✅ got: #t
[🧪 test] The generated records load and validate
✅ got: (Ada Main 1 #t 36)
+----------------------------------------------+
[📑 suite] External references
[🧪 test] An unknown resolution mode is an error
✅ got: 1
[🧪 test] Targeted resolution follows only what is referenced
✅ got: #t
✅ got: #t
✅ got: #f
[🧪 test] Aggressive resolution adds every schema of a referenced file
✅ got: #t
✅ got: #t
[🧪 test] A reference cycle terminates and points both ways
✅ got: #t
✅ got: #t
✅ got: #t
+----------------------------------------------+
[📑 suite] Remote input and authentication
[🧪 test] A remote spec and its relative reference are fetched with auth
✅ got: 0
✅ got: #t
[⚙️ assert] the primary spec was fetched
✅ got: #t
[⚙️ assert] the external schema was fetched
✅ got: #t
✅ got: (bearer secret)
✅ got: (bearer secret)
ℹ️ info - ran 4 tasks in: 1 seconds, -382390 microseconds
running unit tests from file: generator-spec.scm, entrypoint: spec
;;; found fresh local cache at /tmp/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/guile-openapi-generator/trunk/src/openapi-generator/config.scm.go
;;; note: source file ./src/openapi-generator/models.scm
;;; newer than compiled /run/current-system/profile/lib/guile/3.0/site-ccache/openapi-generator/models.go
;;; found fresh local cache at /tmp/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/guile-openapi-generator/trunk/src/openapi-generator/models.scm.go
;;; note: source file ./src/openapi-generator/code.scm
;;; newer than compiled /run/current-system/profile/lib/guile/3.0/site-ccache/openapi-generator/code.go
;;; found fresh local cache at /tmp/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/guile-openapi-generator/trunk/src/openapi-generator/code.scm.go
;;; note: source file ./src/openapi-generator/controllers.scm
;;; newer than compiled /run/current-system/profile/lib/guile/3.0/site-ccache/openapi-generator/controllers.go
;;; found fresh local cache at /tmp/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/guile-openapi-generator/trunk/src/openapi-generator/controllers.scm.go
;;; note: source file ./src/openapi-generator/emitter.scm
;;; newer than compiled /run/current-system/profile/lib/guile/3.0/site-ccache/openapi-generator/emitter.go
;;; found fresh local cache at /tmp/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/guile-openapi-generator/trunk/src/openapi-generator/emitter.scm.go
;;; note: source file ./src/openapi-generator/client.scm
;;; newer than compiled /run/current-system/profile/lib/guile/3.0/site-ccache/openapi-generator/client.go
;;; found fresh local cache at /tmp/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/guile-openapi-generator/trunk/src/openapi-generator/client.scm.go
;;; note: source file ./src/openapi-generator/runtime.scm
;;; newer than compiled /run/current-system/profile/lib/guile/3.0/site-ccache/openapi-generator/runtime.go
;;; found fresh local cache at /tmp/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/guile-openapi-generator/trunk/src/openapi-generator/runtime.scm.go
;;; note: source file ./src/openapi-generator/cli.scm
;;; newer than compiled /run/current-system/profile/lib/guile/3.0/site-ccache/openapi-generator/cli.go
;;; found fresh local cache at /tmp/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/guile-openapi-generator/trunk/src/openapi-generator/cli.scm.go
;;; note: source file ./src/openapi-generator/config-file.scm
;;; newer than compiled /run/current-system/profile/lib/guile/3.0/site-ccache/openapi-generator/config-file.go
;;; found fresh local cache at /tmp/.cache/guile/ccache/3.0-LE-8-4.7/etc/byggsteg/job-clone/guile-openapi-generator/trunk/src/openapi-generator/config-file.scm.go
WARNING: (openapi-generator client): imported module (openapi-generator code) overrides core binding `symbol-append'
openapi-generator: Use only one of #:input, #:document and #:json-schema-file
openapi-generator: Unknown external reference mode sometimes; expected one of none, targeted, aggressive
openapi-generator: Unknown controller backend phoenix; expected one of none, vanilla, artanis
guile-openapi-generator 0.1.0
Read an OpenAPI 3.x specification and generate Guile code,
or read the routes of a server and write a specification.
Usage: openapi-generator generate --input SPEC --output DIR
[options]
openapi-generator spec --input ROUTES --output FILE
[options]
--help (-h) Show this help and exit.
--version (-v) Show the version and exit.
--config VALUE Read options from a YAML or JSON file; flags override it.
--input (-i) VALUE The OpenAPI 3.x specification to read (.yaml, .json, or - for stdin).
--output (-o) VALUE The directory to write the generated code to.
--check Report files that are missing or out of date; write nothing.
--module (-m) VALUE The root module name (default: the API title).
--targets (-t) VALUE Comma-separated: models, controllers, client, readme.
--controllers (-c) VALUE vanilla, artanis or none (default: vanilla).
--http (-H) VALUE guile, curl or none (default: guile).
--no-models Generate no record module.
--model-suffix VALUE Append SUFFIX to every generated model name.
--operation-id-transform VALUE Rename operationIds as REGEX:REPLACEMENT.
--base-url VALUE Override the first server URL of the specification.
--self-contained Copy the runtime modules next to the generated code.
--no-validate Do not check that the input is OpenAPI 3.x.
--no-header Omit the banner comment from generated files.
--header-file VALUE Add the contents of FILE to the generated banner.
--dependencies VALUE none or guix: write a dependencies.scm manifest.
--json-schema-file VALUE Read a JSON Schema document instead of an OpenAPI spec.
--json-schema-root-name VALUE Name the schema generated from a JSON Schema's top level.
--api-fragment VALUE Merge a partial OpenAPI document into the primary one.
--auth VALUE Send 'Name: value' on remote fetches; repeatable.
--resolve-external VALUE none, targeted or aggressive external $ref resolution.
--request-response-models Emit Request and Response model sets.
--exclude-read-only Drop readOnly properties from the single model set.
--exclude-write-only Drop writeOnly properties from the single model set.
--x-extensible-enums Treat x-extensible-enum(s) as enumerations.
--fault-tolerant-enums Map unknown string enum values to 'unrecognized'.
--non-null-response-payloads Fail when a declared success body is missing or null.
Examples:
openapi-generator generate -i petstore.yaml -o gen
openapi-generator generate -i petstore.yaml -o gen -c artanis -H curl
openapi-generator generate --config openapi-generator.yaml
openapi-generator generate -i api.yaml -o gen --check
openapi-generator generate -i api.yaml -o gen -t models,client
openapi-generator spec -i app/controllers.scm -o openapi.yaml
openapi-generator spec -i app/routes.scm -s artanis -t 'My API'
openapi-generator: one of --input or --json-schema-file is required
guile-openapi-generator 0.1.0
Read an OpenAPI 3.x specification and generate Guile code,
or read the routes of a server and write a specification.
Usage: openapi-generator generate --input SPEC --output DIR
[options]
openapi-generator spec --input ROUTES --output FILE
[options]
--help (-h) Show this help and exit.
--version (-v) Show the version and exit.
--config VALUE Read options from a YAML or JSON file; flags override it.
--input (-i) VALUE The OpenAPI 3.x specification to read (.yaml, .json, or - for stdin).
--output (-o) VALUE The directory to write the generated code to.
--check Report files that are missing or out of date; write nothing.
--module (-m) VALUE The root module name (default: the API title).
--targets (-t) VALUE Comma-separated: models, controllers, client, readme.
--controllers (-c) VALUE vanilla, artanis or none (default: vanilla).
--http (-H) VALUE guile, curl or none (default: guile).
--no-models Generate no record module.
--model-suffix VALUE Append SUFFIX to every generated model name.
--operation-id-transform VALUE Rename operationIds as REGEX:REPLACEMENT.
--base-url VALUE Override the first server URL of the specification.
--self-contained Copy the runtime modules next to the generated code.
--no-validate Do not check that the input is OpenAPI 3.x.
--no-header Omit the banner comment from generated files.
--header-file VALUE Add the contents of FILE to the generated banner.
--dependencies VALUE none or guix: write a dependencies.scm manifest.
ℹ️ info - shuffled with seed 198432627 (replay with VERITAS_SEED=198432627)
+----------------------------------------------+
[📑 suite] Self-contained trees
[🧪 test] The copied tree loads with only its own directory on the path
✅ got: (cat Mia #t 9 #t #t)
[🧪 test] The runtime is copied next to the generated code
[⚙️ assert] vendored/jso.scm
✅ got: #t
[⚙️ assert] vendored/runtime.scm
✅ got: #t
[⚙️ assert] vendored/models.scm
✅ got: #t
[🧪 test] The copy imports the copy
✅ got: #t
+----------------------------------------------+
[📑 suite] Generation report
[🧪 test] The client backend changes the generated client
[⚙️ assert] the curl backend was chosen
✅ got: #t
[🧪 test] Models only, when asked
[⚙️ assert] only/models.scm
✅ got: #t
[⚙️ assert] only/README.org
✅ got: #t
[⚙️ assert] only/controllers.scm
✅ got: #f
[⚙️ assert] only/client.scm
✅ got: #f
✅ got: (models readme)
[🧪 test] The README names the API and its operations
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
✅ got: #t
[🧪 test] The Artanis adapter is written when it is asked for
[⚙️ assert] artanis-app/artanis.scm
✅ got: #t
[⚙️ assert] artanis-app/controllers.scm
✅ got: #t
[🧪 test] The module name defaults to the API title
[⚙️ assert] mini/models.scm
✅ got: #t
✅ got: #t
[🧪 test] The documented Todo example generates
✅ got: (models controllers client readme)
✅ got: #t
[🧪 test] A run writes models, controllers, a client and a README
[⚙️ assert] petstore/models.scm
✅ got: #t
[⚙️ assert] petstore/controllers.scm
✅ got: #t
[⚙️ assert] petstore/client.scm
✅ got: #t
[⚙️ assert] petstore/README.org
✅ got: #t
✅ got: 4
✅ got: models
+----------------------------------------------+
[📑 suite] Validating the specification
[🧪 test] #:validate? decides whether an in-memory document is checked
[⚙️ assert] a Swagger document is refused by default
✅ success catching error: misc-error: (#f ~A (<document> declares Swagger 2.0; only OpenAPI 3.x is supported) #f)
[⚙️ assert] skipping validation still runs
✅ got: (readme)
+----------------------------------------------+
[📑 suite] Command line
[🧪 test] generate is an explicit command
✅ got: 0
✅ got: #f
[🧪 test] An unknown controller backend is an error
✅ got: 1
[🧪 test] --help and --version succeed
✅ got: 0
✅ got: 0
[🧪 test] A missing --input is an error
✅ got: 1
[🧪 test] A command word is required
✅ got: 1
[🧪 test] Backend names are parsed
✅ got: artanis
✅ got: none
✅ got: curl
+----------------------------------------------+
[📑 suite] Deterministic, safe writes
[🧪 test] Two runs produce identical trees
[⚙️ assert] stable/models.scm
✅ got: ;; -*- mode: scheme; coding: utf-8 -*-
;;
;; Models generated from test/fixtures/petstore.yaml
;; Generated by guile-openapi-generator 0.1.0, from the Petstore 3.0.3
;; specification. Do not edit this file: regenerate it instead.
(define-module (stable models)
#:use-module (openapi-generator jso)
#:use-module (openapi-generator runtime)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-9)
#:export (<pet>
pet?
make-pet
validate-pet
pet-valid?
pet->jso
jso->pet
pet-apply-patch
pet-id
pet-name
pet-status
pet-status-values
pet-status?
pet-status->jso
jso->pet-status
pet-status-available
pet-status-pending
pet-status-sold
<new-pet>
new-pet?
make-new-pet
validate-new-pet
new-pet-valid?
new-pet->jso
jso->new-pet
new-pet-apply-patch
new-pet-name
new-pet-status
new-pet-status-values
new-pet-status?
new-pet-status->jso
jso->new-pet-status
new-pet-status-available
new-pet-status-pending
new-pet-status-sold))
(define-record-type <pet>
(%make-pet id name status)
pet?
(id pet-id)
(name pet-name)
(status pet-status))
(define-record-type <new-pet>
(%make-new-pet name status)
new-pet?
(name new-pet-name)
(status new-pet-status))
(define* (make-pet #:key (id #f) (name #f) (status #f))
"Create a Pet.
A Pet.
Fields: id (integer, required), name (string, required), status (pet-status).
@example
(make-pet #:id <integer> #:name <string> #:status <pet-status>)
@end example"
(let ((value (%make-pet id name status)))
(validate-pet value)
value))
(define (validate-pet value)
"Check VALUE against the constraints the specification declares for Pet.
Returns #t, or throws 'openapi-validation-error naming the field at fault."
(check-predicate '("pet") value pet? "Pet")
(check-required '("pet" "id") (pet-id value))
(check-type '("pet" "id") (pet-id value) 'integer)
(check-required '("pet" "name") (pet-name value))
(check-type '("pet" "name") (pet-name value) 'string)
(check-min-length '("pet" "name") (pet-name value) 1)
(when (pet-status value)
(check-type '("pet" "status") (pet-status value) 'string)
(check-enum '("pet" "status") (pet-status value) pet-status-values))
#t)
(define (pet-valid? value)
"Return #t when VALUE is a valid Pet."
(valid? (lambda () (validate-pet value))))
(define (pet->jso value)
"Return VALUE as a jso object, ready for JSON serialisation."
(check-predicate '("pet") value pet? "Pet")
(append (list (cons "id" (pet-id value)))
(list (cons "name" (pet-name value)))
(if (eq? (pet-status value) #f)
'()
(list (cons "status" (pet-status->jso (pet-status value)))))))
(define (jso->pet node)
"Build a Pet from NODE, a jso object."
(if (or (json-null? node) (not (jso-object? node)))
#f
(make-pet
#:id
(let ((value (jso-ref node "id")))
(if (json-null? value)
#f
value))
#:name
(let ((value (jso-ref node "name")))
(if (json-null? value)
#f
value))
#:status (jso->pet-status (jso-ref node "status")))))
(define* (pet-apply-patch value patch)
"Return VALUE with the RFC 7386 JSON merge patch PATCH applied.
Members of PATCH set to null are removed, and the result is validated
like any other Pet, so a patch that would leave the record invalid raises."
(jso->pet (json-merge-patch (pet->jso value) patch)))
(define pet-status-values
(list "available" "pending" "sold"))
(define pet-status-available
"available")
(define pet-status-pending
"pending")
(define pet-status-sold
"sold")
(define (pet-status? value)
"Return #t when VALUE is one of the pet.status values."
(and (string? value) (if (member value pet-status-values) #t #f)))
(define (pet-status->jso value)
"Return VALUE as a jso scalar, after checking it."
(if (pet-status? value)
value
(validation-failure '("pet-status")
"must be one of pet-status-values, got ~s" value)))
(define (jso->pet-status node)
"Read a pet.status from the jso scalar NODE."
(if (json-null? node)
#f
node))
(define* (make-new-pet #:key (name #f) (status #f))
"Create a NewPet.
A NewPet.
Fields: name (string, required), status (new-pet-status).
@example
(make-new-pet #:name <string> #:status <new-pet-status>)
@end example"
(let ((value (%make-new-pet name status)))
(validate-new-pet value)
value))
(define (validate-new-pet value)
"Check VALUE against the constraints the specification declares for NewPet.
Returns #t, or throws 'openapi-validation-error naming the field at fault."
(check-predicate '("new-pet") value new-pet? "NewPet")
(check-required '("new-pet" "name") (new-pet-name value))
(check-type '("new-pet" "name") (new-pet-name value) 'string)
(when (new-pet-status value)
(check-type '("new-pet" "status") (new-pet-status value) 'string)
(check-enum '("new-pet" "status") (new-pet-status value)
new-pet-status-values))
#t)
(define (new-pet-valid? value)
"Return #t when VALUE is a valid NewPet."
(valid? (lambda () (validate-new-pet value))))
(define (new-pet->jso value)
"Return VALUE as a jso object, ready for JSON serialisation."
(check-predicate '("new-pet") value new-pet? "NewPet")
(append (list (cons "name" (new-pet-name value)))
(if (eq? (new-pet-status value) #f)
'()
(list
(cons "status"
(new-pet-status->jso (new-pet-status value)))))))
(define (jso->new-pet node)
"Build a NewPet from NODE, a jso object."
(if (or (json-null? node) (not (jso-object? node)))
#f
(make-new-pet
#:name
(let ((value (jso-ref node "name")))
(if (json-null? value)
#f
value))
#:status (jso->new-pet-status (jso-ref node "status")))))
(define* (new-pet-apply-patch value patch)
"Return VALUE with the RFC 7386 JSON merge patch PATCH applied.
Members of PATCH set to null are removed, and the result is validated
like any other NewPet, so a patch that would leave the record invalid raises."
(jso->new-pet (json-merge-patch (new-pet->jso value) patch)))
(define new-pet-status-values
(list "available" "pending" "sold"))
(define new-pet-status-available
"available")
(define new-pet-status-pending
"pending")
(define new-pet-status-sold
"sold")
(define (new-pet-status? value)
"Return #t when VALUE is one of the new-pet.status values."
(and (string? value) (if (member value new-pet-status-values) #t #f)))
(define (new-pet-status->jso value)
"Return VALUE as a jso scalar, after checking it."
(if (new-pet-status? value)
value
(validation-failure '("new-pet-status")
"must be one of new-pet-status-values, got ~s" value)))
(define (jso->new-pet-status node)
"Read a new-pet.status from the jso scalar NODE."
(if (json-null? node)
#f
node))
[⚙️ assert] stable/controllers.scm
✅ got: ;; -*- mode: scheme; coding: utf-8 -*-
;; Controllers generated from test/fixtures/petstore.yaml
;; Generated by guile-openapi-generator 0.1.0, from the Petstore 3.0.3
;; specification. Do not edit this file: regenerate it instead.
(define-module (stable controllers)
#:use-module (openapi-generator json)
#:use-module (openapi-generator jso)
#:use-module (openapi-generator runtime)
#:use-module (stable models)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-9)
#:use-module (ice-9 regex)
#:use-module (ice-9 format)
#:use-module (rnrs bytevectors)
#:use-module (web request)
#:use-module (web response)
#:use-module (web uri)
#:use-module (web http)
#:export (
<route>
route?
make-route
route-method
route-pattern
route-parameters
route-operation
api-handlers
invoke-handler
find-route
dispatch-route
dispatch
guile-handler
routes
list-pets
post-pets
get-pet-by-id
delete-pets-by-pet-id))
(define-record-type <route>
(make-route method pattern parameters operation)
route?
(method route-method)
(pattern route-pattern)
(parameters route-parameters)
(operation route-operation))
;; The route table of the specification, in declaration order.
(define routes
(list
(make-route 'GET (make-regexp "^/pets/?$") '() 'list-pets)
(make-route 'POST (make-regexp "^/pets/?$") '() 'post-pets)
(make-route 'GET (make-regexp "^/pets/([^/]+)/?$") '("petId") 'get-pet-by-id)
(make-route 'DELETE (make-regexp "^/pets/([^/]+)/?$") '("petId") 'delete-pets-by-pet-id)))
;; An alist of operation name to handler procedure.
;;
;; A handler is called as (handler request arguments); arguments holds the
;; decoded parameters keyed by field name and, when the operation declares a
;; request body, a body member with the decoded record. A handler returns an
;; <http-response>, which ok, created, no-content and error-response build.
(define api-handlers (make-parameter '()))
(define* (invoke-handler operation request arguments)
"Call the handler of OPERATION, or raise openapi-not-implemented.
The api-handlers parameter holds the table, so a test can parameterize it and
a server can install its handlers once at start up."
(let ((handler (assq-ref (api-handlers) operation)))
(if handler
(handler request arguments)
(unimplemented-operation operation request))))
(define (find-route request)
"Return the first route matching REQUEST, or #f."
(let ((method (http-request-method request))
(path (http-request-path request)))
(find (lambda (route)
(and (eq? (route-method route) method)
(regexp-exec (route-pattern route) path)))
routes)))
(define (dispatch-route route request)
"Call the operation of ROUTE with the arguments captured from REQUEST."
(let* ((match (regexp-exec (route-pattern route)
(http-request-path request)))
(captured (map (lambda (index)
(match:substring match index))
(iota (length (route-parameters route)) 1)))
(path-arguments (map cons (route-parameters route) captured)))
(case (route-operation route)
((list-pets) (list-pets request path-arguments))
((post-pets) (post-pets request path-arguments))
((get-pet-by-id) (get-pet-by-id request path-arguments))
((delete-pets-by-pet-id) (delete-pets-by-pet-id request path-arguments))
(else (unimplemented-operation (route-operation route) request)))))
(define* (dispatch request #:key (handlers (api-handlers)))
"Answer REQUEST with the response of the handler of the matching route.
A method and path that match no route raises 'openapi-no-route; a matched
route whose operation has no handler raises 'openapi-not-implemented."
(let ((route (find-route request)))
(unless route
(throw 'openapi-no-route
(http-request-method request)
(http-request-path request)))
(parameterize ((api-handlers handlers))
(dispatch-route route request))))
(define (guile-header-value value)
"Return VALUE, a header parsed by (web http), as its text."
(cond
((string? value) value)
((pair? value)
(let ((parameters (cdr value)))
(string-append
(format #f "~a" (car value))
(if (list? parameters)
(apply string-append
(map (lambda (parameter)
(if (pair? parameter)
(format #f "; ~a=~a"
(car parameter) (cdr parameter))
(format #f "; ~a" parameter)))
parameters))
""))))
(else (format #f "~a" value))))
(define (guile-request-body body content-type)
"Return BODY, as (web server) read it, in the shape the runtime expects."
(if (and (bytevector? body)
(or (string-contains content-type "json")
(string-contains content-type "text/")
(string-contains content-type
"x-www-form-urlencoded")))
(utf8->string body)
body))
(define (guile-request request body)
"Return the <http-request> that corresponds to a (web request) REQUEST."
(let* ((uri (request-uri request))
(headers (map (lambda (entry)
(cons (format #f "~a" (car entry))
(guile-header-value (cdr entry))))
(request-headers request)))
(content-type (or (header-ref headers "Content-Type") "")))
(make-http-request
(request-method request)
(uri-path uri)
(parse-query (uri-query uri))
headers
(guile-request-body body content-type))))
(define (guile-response-headers response)
"Return the headers of RESPONSE as (web response) wants them."
(map (lambda (entry)
(let ((name (string->symbol
(string-downcase (format #f "~a" (car entry))))))
(cons name
(if (string? (cdr entry))
(parse-header name (cdr entry))
(cdr entry)))))
(http-response-headers response)))
(define (guile-response-body response)
"Return the body of RESPONSE as (web server) can send it."
(let ((body (http-response-body response))
(content-type (guile-header-value
(or (http-response-header response "Content-Type")
""))))
(cond
((or (string? body) (bytevector? body)) body)
((or (eq? body #f) (json-null? body)) "")
((string-contains content-type "json") (jso->json-string body))
(else (format #f "~a" body)))))
(define (guile-handler)
"Return a handler for (web server)'s run-server.
Install the handler of every operation in api-handlers first, then pass the
result to run-server:
(parameterize ((api-handlers (list (cons 'list-pets my-handler))))
(run-server (guile-handler) 'http '(#:port 8080)))"
(lambda (request body)
(let ((response (dispatch (guile-request request body))))
(values (build-response
#:code (http-response-status response)
#:headers (guile-response-headers response))
(guile-response-body response)))))
(define (list-pets request path-arguments)
"GET /pets
Parameters:
- limit (integer, query)
The arguments are collected into an alist and passed to the handler
installed in api-handlers for this operation."
(invoke-handler 'list-pets request
(list
(cons 'limit (let ((raw (http-request-query-ref request "limit"))) (if raw (let ((number (string->number raw))) (if number number raw)) #f))))))
(define (post-pets request path-arguments)
"POST /pets
Create a pet
The request body is decoded from application/json.
The arguments are collected into an alist and passed to the handler
installed in api-handlers for this operation."
(invoke-handler 'post-pets request
(list
(cons 'body (let ((node (let ((raw (http-request-body request))) (cond ((string? raw) (json-string->jso raw)) ((jso-object? raw) raw) (else #f))))) (if (eq? node #f) #f (jso->new-pet node)))))))
(define (get-pet-by-id request path-arguments)
"GET /pets/{petId}
Parameters:
- petId (integer, required, path)
The arguments are collected into an alist and passed to the handler
installed in api-handlers for this operation."
(invoke-handler 'get-pet-by-id request
(list
(cons 'pet-id (let ((number (string->number (assoc-ref path-arguments "petId")))) (if number number (assoc-ref path-arguments "petId")))))))
(define (delete-pets-by-pet-id request path-arguments)
"DELETE /pets/{petId}
Parameters:
- petId (integer, required, path)
The arguments are collected into an alist and passed to the handler
installed in api-handlers for this operation."
(invoke-handler 'delete-pets-by-pet-id request
(list
(cons 'pet-id (let ((number (string->number (assoc-ref path-arguments "petId")))) (if number number (assoc-ref path-arguments "petId")))))))
[⚙️ assert] stable/client.scm
✅ got: ;; HTTP client generated from test/fixtures/petstore.yaml
;; Generated by guile-openapi-generator 0.1.0, from the Petstore 3.0.3 specification. Do not edit this file: regenerate it instead.
;; The transport is (web client) from Guile itself, so this file needs nothing but Guile and guile-json.
(define-module (stable client)
#:use-module (openapi-generator json)
#:use-module (openapi-generator jso)
#:use-module (openapi-generator runtime)
#:use-module (stable models)
#:use-module (web client)
#:use-module (web response)
#:use-module (web uri)
#:use-module (ice-9 iconv)
#:use-module (rnrs bytevectors)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-9)
#:use-module (srfi srfi-13)
#:use-module (ice-9 receive)
#:export (<api-client>
api-client?
make-api-client
api-client-base-url
api-client-auth
api-client-headers
api-client-timeout
api-security-schemes
client-url
client-send
list-pets
post-pets
get-pet-by-id
delete-pets-by-pet-id))
;; The security schemes of the specification, as authenticate wants them.
(define api-security-schemes
'(("api_key" ("type" . "apiKey") ("in" . "header") ("name" . "X-API-Key"))
("bearer"
("type" . "http")
("scheme" . "bearer")
("bearerFormat" . "JWT"))
("oauth" ("type" . "oauth2"))))
(define-record-type <api-client>
(%make-api-client base-url auth headers timeout)
api-client?
(base-url api-client-base-url)
(auth api-client-auth)
(headers api-client-headers)
(timeout api-client-timeout))
(define* (make-api-client #:key
(base-url "https://api.example.com/v1")
(auth #f)
(headers '())
(timeout 30))
"Create a client for this API.
BASE-URL prefixes every path. AUTH holds the credentials, or #f to call only
public operations; HEADERS are sent with every request; TIMEOUT is the request
timeout in seconds."
(%make-api-client base-url auth headers timeout))
(define (client-url client path query)
"Return the absolute URL of PATH, with the QUERY alist rendered."
(let ((base (string-trim-right (api-client-base-url client) #\/))
(parameters (query-string query)))
(if (string-null? parameters)
(string-append base path)
(string-append base path "?" parameters))))
(define (encode-request-body body encoding)
"Return BODY encoded as the request body ENCODING names, as bytes."
(case encoding
((json) (string->utf8 (jso->json-string body)))
((form) (string->utf8 (form-string body)))
((binary raw) (if (bytevector? body) body (string->utf8 body)))
((text) (string->utf8 body))
(else body)))
(define (response-text body)
"Return BODY, a string or a bytevector, as text."
(cond
((string? body) body)
((bytevector? body) (utf8->string body))
(else "")))
(define (encoding-for-content-type content-type default)
"Return the encoding implied by CONTENT-TYPE, or DEFAULT when it says nothing."
(let ((text
(string-downcase
(if (string? content-type)
content-type
(format #f "~a" (or content-type ""))))))
(cond
((or (string-contains text "application/json") (string-contains text "+json"))
'json)
((string-contains text "text/") 'text)
((string-contains text "x-www-form-urlencoded") 'form)
((string-contains text "multipart/") 'multipart)
(else default))))
(define (decode-response-body body encoding)
"Return BODY decoded as the response ENCODING names."
(case encoding
((json)
(let ((text (response-text body)))
(if (string-null? (string-trim-both text))
json-null
(json-string->jso text))))
((text) (response-text body))
((form) (parse-query (response-text body)))
((multipart binary) body)
(else body)))
(define (content-type-header value)
"Return VALUE, a Content-Type string, in (web http)'s representation."
(let* ((parts (map string-trim-both (string-split value #\;)))
(media (string->symbol (string-downcase (car parts))))
(parameters (map content-type-parameter (cdr parts))))
(cons media parameters)))
(define (content-type-parameter parameter)
"Return one NAME=VALUE content-type parameter as a pair."
(let ((equals (string-index parameter #\=)))
(if equals
(cons
(string->symbol
(string-downcase
(string-trim-both
(substring
parameter
0
equals))))
(content-type-value
(string-trim-both
(substring parameter
(+ equals 1)
(string-length
parameter)))))
(cons (string->symbol (string-downcase parameter)) #t))))
(define (content-type-value value)
"Return VALUE without the quotes a content-type parameter may carry."
(if (and (> (string-length value) 1) (char=? (string-ref value 0) #\")
(char=? (string-ref value (- (string-length value) 1)) #\"))
(substring value 1 (- (string-length value) 1))
value))
(define* (client-send
client
method
path
#:key
query
headers
body
(body-encoding 'json)
(content-type "application/json")
(accept "application/json")
(response-encoding 'json))
"Send METHOD to PATH and return the <http-response>."
(let* ((url (client-url client path query))
(payload (and body (encode-request-body body body-encoding)))
(headers
(if (and body content-type
(not (header-ref headers "Content-Type")))
(header-set headers "Content-Type" content-type)
headers))
(headers
(if (and accept (not (header-ref headers "Accept")))
(header-set headers "Accept" accept)
headers))
(request-headers
(merge-cookie-headers (append headers (api-client-headers client)))))
(receive (response response-body)
(http-request url
#:method method
#:headers (guile-headers request-headers)
#:body payload)
(make-http-response (response-code response) (response-headers response)
(decode-response-body response-body
(encoding-for-content-type
(header-ref
(response-headers
response)
"Content-Type")
response-encoding))))))
(define (guile-headers headers)
"Return HEADERS as (web client) wants them.
@code{content-type} is lowered and parsed because Guile's client insists on its
own representation of that one header; the rest keep a title-case name and a
string value, which is written through unchanged."
(map
(lambda (header)
(let ((name (string-downcase (car header)))
(value (cdr header)))
(if (string=? name "content-type")
(cons 'content-type (content-type-header value))
(cons (string->symbol (header-name (car header))) value))))
headers))
(define (header-name name)
"Return NAME with each hyphen-separated part capitalised."
(header-name-loop (string->list (string-downcase name)) #t '()))
(define (header-name-loop characters start? out)
"Walk CHARACTERS, capitalising the first letter of each part."
(if (null? characters)
(list->string (reverse out))
(let ((character (car characters)))
(cond
((char=? character #\-)
(header-name-loop (cdr characters) #t
(cons character out)))
(start?
(header-name-loop (cdr characters) #f
(cons (char-upcase character) out)))
(else (header-name-loop (cdr characters) #f (cons character out)))))))
(define* (list-pets client #:key (limit #f) (auth (api-client-auth client)))
"GET /pets
Parameters:
- limit (integer, query)
Returns two values: the decoded body (a list of Pet) and the <http-response>.
Credentials come from AUTH, which defaults to the client's."
(receive (auth-headers auth-query)
(authenticate auth api-security-schemes '((("api_key"))))
(let ((path "/pets"))
(let ((response
(client-send client 'GET path
#:query
(append (list (cons "limit" limit)) auth-query)
#:headers auth-headers
#:accept "application/json"
#:response-encoding 'json)))
(expect-status response '(200))
(values
(let ((value (http-response-body response)))
(if (or (eq? value #f) (json-null? value))
#f
(map (lambda (element) (jso->pet element))
(jso-array->list value)))) response)))))
(define* (post-pets client #:key body (auth (api-client-auth client)))
"POST /pets
Create a pet
The request body is encoded as application/json.
Returns two values: the decoded body (a Pet) and the <http-response>.
Credentials come from AUTH, which defaults to the client's."
(receive (auth-headers auth-query)
(authenticate auth api-security-schemes '((("api_key"))))
(let ((path "/pets"))
(let ((response
(client-send client 'POST path
#:query auth-query
#:headers auth-headers
#:accept "application/json"
#:response-encoding 'json
#:body (new-pet->jso body)
#:body-encoding 'json
#:content-type "application/json")))
(expect-status response '(201))
(values (jso->pet (http-response-body response)) response)))))
(define* (get-pet-by-id client #:key pet-id (auth (api-client-auth client)))
"GET /pets/{petId}
Parameters:
- petId (integer, required, path)
Returns two values: the decoded body (a Pet) and the <http-response>.
Credentials come from AUTH, which defaults to the client's."
(receive (auth-headers auth-query)
(authenticate auth api-security-schemes '((("api_key"))))
(let ((path (params->path "/pets/{petId}" (list (cons "petId" pet-id)))))
(let ((response
(client-send client 'GET path
#:query auth-query
#:headers auth-headers
#:accept "application/json"
#:response-encoding 'json)))
(expect-status response '(200))
(values (jso->pet (http-response-body response)) response)))))
(define* (delete-pets-by-pet-id
client
#:key
pet-id
(auth (api-client-auth client)))
"DELETE /pets/{petId}
Parameters:
- petId (integer, required, path)
Returns two values: the decoded body and the <http-response>.
Credentials come from AUTH, which defaults to the client's."
(receive (auth-headers auth-query)
(authenticate auth api-security-schemes '())
(let ((path (params->path "/pets/{petId}" (list (cons "petId" pet-id)))))
(let ((response
(client-send client 'DELETE path
#:query auth-query
#:headers auth-headers
#:accept "application/json"
#:response-encoding 'json)))
(expect-status response '(204))
(values (http-response-body response) response)))))
[⚙️ assert] stable/README.org
✅ got: #+title: Petstore
#+options: toc:nil
* Petstore
Generated from test/fixtures/petstore.yaml by guile-openapi-generator 0.1.0.
* Modules
- =stable/models.scm= --- records, validations and JSON conversion
- =stable/controllers.scm= --- route table and dispatcher
- =stable/client.scm= --- HTTP client (plain Guile)
* Operations
| Method | Path | Procedure |
|--------+------+-----------|
| GET | /pets | =list-pets= |
| POST | /pets | =post-pets= |
| GET | /pets/{petId} | =get-pet-by-id= |
| DELETE | /pets/{petId} | =delete-pets-by-pet-id= |
* Schemas
- =Pet= --- =pet= records
- =NewPet= --- =new-pet= records
* Using the code
#+begin_src scheme
(use-modules (stable models))
(define value (make-pet #:name "..."))
(use-modules (stable client) (openapi-generator runtime))
(define client (make-api-client))
(use-modules (stable controllers))
#+end_src
* Serving the API
The controllers module exports =guile-handler=, an adapter for
Guile's own =run-server=. Install one handler per operation and
start the server:
#+begin_src scheme
(use-modules (web server)
(stable controllers)
(openapi-generator runtime))
(parameterize ((api-handlers
(list (cons 'list-pets
(lambda (request arguments)
(ok '#()))))))
(run-server (guile-handler) 'http '(#:port 8080)))
#+end_src
The handler receives the decoded arguments and returns a
response built with =ok=, =created=, =text-response=,
=binary-response= or =error-response=. JSON bodies are serialised
for you; convert a record with its =->jso= procedure first.
* Calling the API
A client procedure returns two values: the decoded body and the
<http-response>.
#+begin_src scheme
(use-modules (ice-9 receive)
(stable client))
(define client (make-api-client))
(receive (value response)
(list-pets client)
value)
#+end_src
Pass the keyword arguments the operation declares, for example
=#:limit 10=. =make-api-client= starts from the first server URL of
the specification; pass =#:base-url= to point it elsewhere.
* Packaging
- =--self-contained= copies the runtime modules next to the generated code,
so the tree needs only Guile and guile-json at run time.
- =--dependencies guix= writes a =dependencies.scm= manifest for
=guix shell -m=.
- By default the modules import =(openapi-generator runtime)=; keep this
generator on the load path, or generate again with =--self-contained=.
A constructor validates: it either returns a record that satisfies
the specification, or throws =openapi-validation-error= naming the
field at fault.
[🧪 test] --input - reads the specification from standard input
✅ got: 0
✅ got: #t
[🧪 test] --check reports a stale file and leaves it alone
✅ got: 1
[⚙️ assert] --check writes nothing
✅ got: stale
[🧪 test] --check accepts a tree it just wrote
✅ got: 0
✅ got: 0
+----------------------------------------------+
[📑 suite] Configuration
[🧪 test] --model-suffix renames the models
✅ got: 0
✅ got: #t
✅ got: #t
[🧪 test] The command line overrides the config file
✅ got: 0
✅ got: #t
[⚙️ assert] the config file's guix dependencies are written
✅ got: #t
✅ got: #f
[🧪 test] --no-header omits the banner and --header-file adds a paragraph
✅ got: 0
✅ got: #f
✅ got: 0
✅ got: #t
[🧪 test] --dependencies guix writes a manifest
✅ got: 0
✅ got: #t
✅ got: #t
[🧪 test] A JSON config file is read the same way
✅ got: json-configured
✅ got: Json
✅ got: (models)
✅ got: #f
[🧪 test] A config file is read with normalised keys
✅ got: configured
✅ got: Dto
✅ got: (models readme)
✅ got: guix
[🧪 test] --no-validate skips the OpenAPI check
✅ got: 1
✅ got: 0
[🧪 test] An unknown target is an error
✅ got: 1
[🧪 test] A client target brings the models it needs
✅ got: 0
✅ got: #t
✅ got: #t
✅ got: #f
[🧪 test] --operation-id-transform renames the procedures
✅ got: 0
✅ got: #t
✅ got: #f
[🧪 test] A replacement template can refer to a capture group
✅ got: v2-pets
✅ got: create-pet
[🧪 test] --targets selects the parts to write
✅ got: 0
✅ got: #t
✅ got: #f
✅ got: #f
✅ got: #f
+----------------------------------------------+
[📑 suite] Version
[🧪 test] The generator reports its own version
✅ got: #t
✅ got: #f
ℹ️ info - ran 7 tasks in: 6 seconds, -230283 microseconds
finished running tests
--json-schema-file VALUE Read a JSON Schema document instead of an OpenAPI spec.
--json-schema-root-name VALUE Name the schema generated from a JSON Schema's top level.
--api-fragment VALUE Merge a partial OpenAPI document into the primary one.
--auth VALUE Send 'Name: value' on remote fetches; repeatable.
--resolve-external VALUE none, targeted or aggressive external $ref resolution.
--request-response-models Emit Request and Response model sets.
--exclude-read-only Drop readOnly properties from the single model set.
--exclude-write-only Drop writeOnly properties from the single model set.
--x-extensible-enums Treat x-extensible-enum(s) as enumerations.
--fault-tolerant-enums Map unknown string enum values to 'unrecognized'.
--non-null-response-payloads Fail when a declared success body is missing or null.
Examples:
openapi-generator generate -i petstore.yaml -o gen
openapi-generator generate -i petstore.yaml -o gen -c artanis -H curl
openapi-generator generate --config openapi-generator.yaml
openapi-generator generate -i api.yaml -o gen --check
openapi-generator generate -i api.yaml -o gen -t models,client
openapi-generator spec -i app/controllers.scm -o openapi.yaml
openapi-generator spec -i app/routes.scm -s artanis -t 'My API'
openapi-generator: expected a command: generate or spec
models: /tmp/guile-openapi-generator/check-stale-15801-33-1794339928/checked/models.scm is not up to date
1 generated file(s) need to be regenerated
openapi-generator: test/fixtures/swagger2.yaml declares Swagger 2.0; only OpenAPI 3.x is supported
openapi-generator: Unknown target clients; expected one of models, controllers, client, readme
--------------------------------------------------------------
Maak 0.14.0: the infinitely extensible command runner
Loading tasks from Maak file: /etc/byggsteg/job-clone/guile-openapi-generator/trunk/maak.scm
Executing Maak tasks: test
--------------------------------------------------------------
=> Running Maak task: test
Run the project's Veritas test suites.
Executing: guix time-machine --channels=channels.scm -- shell -m manifest.scm -- guile -L ./src -L ./test -c '((@ (veritas runner) run-tests))'