From 081fe8bb9ff031a6b1a17f81bb0417fabfed1d87 Mon Sep 17 00:00:00 2001 From: Gabriel Omar Cotelli Date: Fri, 19 May 2023 16:04:31 -0300 Subject: [PATCH 1/6] Add Pharo 11 to build matrix Use GitHub actions instead of travis Run tests for Pharo 9, 10 and 11 also Also tests each group loading Add Markdown linter Improve the docs a bit --- .github/workflows/loading-groups.yml | 23 +++ .github/workflows/markdown-lint.yml | 14 ++ .github/workflows/unit-tests.yml | 26 ++++ .smalltalkci/loading.deployment.ston | 13 ++ .smalltalkci/loading.development.ston | 16 ++ .../loading.tests.ston | 11 +- .smalltalkci/loading.tools.ston | 13 ++ .smalltalkci/unit-tests.ston | 16 ++ .travis.yml | 13 -- CONTRIBUTING.md | 31 ++-- README.md | 41 +++-- docs/Installation.md | 38 ++--- docs/UserGuide.md | 140 +++++++++++------- 13 files changed, 285 insertions(+), 110 deletions(-) create mode 100644 .github/workflows/loading-groups.yml create mode 100644 .github/workflows/markdown-lint.yml create mode 100644 .github/workflows/unit-tests.yml create mode 100644 .smalltalkci/loading.deployment.ston create mode 100644 .smalltalkci/loading.development.ston rename .smalltalk.ston => .smalltalkci/loading.tests.ston (55%) create mode 100644 .smalltalkci/loading.tools.ston create mode 100644 .smalltalkci/unit-tests.ston delete mode 100644 .travis.yml diff --git a/.github/workflows/loading-groups.yml b/.github/workflows/loading-groups.yml new file mode 100644 index 0000000..52be705 --- /dev/null +++ b/.github/workflows/loading-groups.yml @@ -0,0 +1,23 @@ +name: Baseline Groups + +on: [push,pull_request,workflow_dispatch] + +jobs: + group-loading: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + smalltalk: [ Pharo64-11, Pharo64-10, Pharo64-9.0, Pharo64-8.0, Pharo64-7.0 ] + load-spec: [ deployment, tests, tools, development ] + name: ${{ matrix.smalltalk }} + ${{ matrix.load-spec }} + steps: + - uses: actions/checkout@v3 + - uses: hpi-swa/setup-smalltalkCI@v1 + with: + smalltalk-image: ${{ matrix.smalltalk }} + - name: Load group in image + run: smalltalkci -s ${{ matrix.smalltalk }} .smalltalkci/loading.${{ matrix.load-spec }}.ston + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + timeout-minutes: 15 diff --git a/.github/workflows/markdown-lint.yml b/.github/workflows/markdown-lint.yml new file mode 100644 index 0000000..fbb50fa --- /dev/null +++ b/.github/workflows/markdown-lint.yml @@ -0,0 +1,14 @@ +name: Markdown Lint +on: [push,pull_request,workflow_dispatch] +jobs: + remark-lint: + name: runner / markdownlint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: markdownlint + uses: reviewdog/action-markdownlint@v0 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + fail_on_error: true + reporter: github-pr-review diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml new file mode 100644 index 0000000..d4fd830 --- /dev/null +++ b/.github/workflows/unit-tests.yml @@ -0,0 +1,26 @@ +name: Unit Tests + +on: [push,pull_request,workflow_dispatch] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + smalltalk: [ Pharo64-11, Pharo64-10, Pharo64-9.0, Pharo64-8.0, Pharo64-7.0 ] + name: ${{ matrix.smalltalk }} + steps: + - uses: actions/checkout@v3 + - name: Set up Smalltalk CI + uses: hpi-swa/setup-smalltalkCI@v1 + with: + smalltalk-image: ${{ matrix.smalltalk }} + - name: Load Image and Run Tests + run: smalltalkci -s ${{ matrix.smalltalk }} .smalltalkci/unit-tests.ston + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + timeout-minutes: 15 + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + name: Unit-Tests-${{matrix.smalltalk}} diff --git a/.smalltalkci/loading.deployment.ston b/.smalltalkci/loading.deployment.ston new file mode 100644 index 0000000..fad82b2 --- /dev/null +++ b/.smalltalkci/loading.deployment.ston @@ -0,0 +1,13 @@ +SmalltalkCISpec { + #loading : [ + SCIMetacelloLoadSpec { + #baseline : 'Teapot', + #directory : '../source', + #load : [ 'Deployment' ], + #platforms : [ #pharo ] + } + ], + #testing : { + #failOnZeroTests : false + } +} diff --git a/.smalltalkci/loading.development.ston b/.smalltalkci/loading.development.ston new file mode 100644 index 0000000..3087562 --- /dev/null +++ b/.smalltalkci/loading.development.ston @@ -0,0 +1,16 @@ +SmalltalkCISpec { + #loading : [ + SCIMetacelloLoadSpec { + #baseline : 'Teapot', + #directory : '../source', + #load : [ 'Development' ], + #platforms : [ #pharo ] + } + ], + #testing : { + #coverage : { + #packages : [ 'Teapot*' ], + #format: #lcov + } + } +} diff --git a/.smalltalk.ston b/.smalltalkci/loading.tests.ston similarity index 55% rename from .smalltalk.ston rename to .smalltalkci/loading.tests.ston index fa2d6ae..b776113 100644 --- a/.smalltalk.ston +++ b/.smalltalkci/loading.tests.ston @@ -2,14 +2,15 @@ SmalltalkCISpec { #loading : [ SCIMetacelloLoadSpec { #baseline : 'Teapot', - #directory : 'source', + #directory : '../source', #load : [ 'Tests' ], - #platforms : [ #pharo ] - } - ], + #platforms : [ #pharo ] + } + ], #testing : { #coverage : { - #packages : [ 'Teapot-Core' ] + #packages : [ 'Teapot*' ], + #format: #lcov } } } diff --git a/.smalltalkci/loading.tools.ston b/.smalltalkci/loading.tools.ston new file mode 100644 index 0000000..50b597f --- /dev/null +++ b/.smalltalkci/loading.tools.ston @@ -0,0 +1,13 @@ +SmalltalkCISpec { + #loading : [ + SCIMetacelloLoadSpec { + #baseline : 'Teapot', + #directory : '../source', + #load : [ 'Tools' ], + #platforms : [ #pharo ] + } + ], + #testing : { + #failOnZeroTests : false + } +} diff --git a/.smalltalkci/unit-tests.ston b/.smalltalkci/unit-tests.ston new file mode 100644 index 0000000..b776113 --- /dev/null +++ b/.smalltalkci/unit-tests.ston @@ -0,0 +1,16 @@ +SmalltalkCISpec { + #loading : [ + SCIMetacelloLoadSpec { + #baseline : 'Teapot', + #directory : '../source', + #load : [ 'Tests' ], + #platforms : [ #pharo ] + } + ], + #testing : { + #coverage : { + #packages : [ 'Teapot*' ], + #format: #lcov + } + } +} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 1ee5dca..0000000 --- a/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ -language: smalltalk -sudo: false - -os: - - linux - -smalltalk: - - Pharo-6.1 - - Pharo-7.0 - - Pharo64-7.0 - -matrix: - fash_finish: true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fc2cbe8..fff4918 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,24 +1,33 @@ -Contributing -============ +# Contributing -There's several ways to contribute to the project: reporting bugs, sending feedback, proposing ideas for new features, fixing or adding documentation, promoting the project, or even contributing code. +There are several ways to contribute to the project: reporting bugs, sending +feedback, proposing ideas for new features, fixing or adding documentation, +promoting the project, or even contributing code. ## Reporting issues You can report issues [here](https://github.com/zeroflag/Teapot/issues/new) ## Contributing Code + - This project is MIT licensed, so any code contribution MUST be under the same license. -- This project uses [Semantic Versioning](http://semver.org/), so keep it in mind when you make backwards-incompatible changes. If some backwards incompatible change is made the major version MUST be increased. -- The source code is hosted in this repository using the Tonel format in the `source` folder. -- The master branch contains the latest changes and should always be in a releasable state. +- This project uses [Semantic Versioning](http://semver.org/), so keep it in + mind when you make backwards-incompatible changes. If some backwards incompatible + change is made the major version MUST be increased. +- The source code is hosted in this repository using the Tonel format in the + `source` folder. +- The master branch contains the latest changes and should always be in a + releasable state. - Feel free to send pull requests or fork the project. -- Code contributions without test cases have a lower probability of being merged into the main branch. +- Code contributions without test cases have a lower probability of being merged + into the main branch. ### Using Iceberg + 1. Download a [Pharo Image and VM](https://get.pharo.org/64) 2. Clone the project or your fork using Iceberg -3. Open the Working Copy and using the contextual menu select `Metacello -> Install baseline...` +3. Open the Working Copy and using the contextual menu select + `Metacello -> Install baseline...` 4. Input `Development` 5. This will load the base code and the test cases 6. Create a new branch to host your code changes @@ -29,5 +38,7 @@ You can report issues [here](https://github.com/zeroflag/Teapot/issues/new) ## Contributing documentation -The project documentation is maintained in this repository in the `docs` folder and licensed under CC BY-SA 4.0. To contribute some documentation or improve the existing, feel free to create a branch or fork this repository, make your changes and send a pull request. - +The project documentation is maintained in this repository in the `docs` folder +and licensed under CC BY-SA 4.0. To contribute some documentation or improve the +existing, feel free to create a branch or fork this repository, make your changes +and send a pull request. diff --git a/README.md b/README.md index a0b7def..a4313ba 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,50 @@ # Teapot -Teapot is micro web framework for [Pharo Smalltalk](https://pharo.org) on top of the [Zinc HTTP components](https://github.com/svenvc/zinc), that focuses on simplicity and ease of use. It's around 600 lines of code, not counting the tests. +Teapot is micro web framework for [Pharo Smalltalk](https://pharo.org) on top of +the [Zinc HTTP components](https://github.com/svenvc/zinc), that focuses on +simplicity and ease of use. It's around 600 lines of code, not counting the tests. **[Explore the docs](/docs)** -[![Build Status](https://travis-ci.com/zeroflag/Teapot.svg?branch=master)](https://travis-ci.com/zeroflag/Teapot) -[![Coverage Status](https://coveralls.io/repos/github/zeroflag/Teapot/badge.svg?branch=master)](https://coveralls.io/github/zeroflag/Teapot?branch=master) +[![Unit Tests](https://github.com/zeroflag/Teapot/actions/workflows/unit-tests.yml/badge.svg)](https://github.com/zeroflag/Teapot/actions/workflows/unit-tests.yml/badge.svg) +[![Coverage Status](https://codecov.io/github/zeroflag/Teapot/coverage.svg?branch=master)](https://codecov.io/gh/zeroflag/Teapot/branch/master) +[![Baseline Groups](https://github.com/zeroflag/Teapot/actions/workflows/loading-groups.yml/badge.svg)](https://github.com/zeroflag/Teapot/actions/workflows/loading-groups.yml) +[![Markdown Lint](https://github.com/zeroflag/Teapot/actions/workflows/markdown-lint.yml/badge.svg)](https://github.com/zeroflag/Teapot/actions/workflows/markdown-lint.yml) -> *Name origin*: [418 I'm a teapot](http://en.wikipedia.org/wiki/List_of_HTTP_status_codes) (RFC 2324) is an HTTP status code. +[![GitHub release](https://img.shields.io/github/release/zeroflag/Teapot.svg)](https://github.com/zeroflag/Teapot/releases/latest) +[![Pharo 9.0](https://img.shields.io/badge/Pharo-9.0-informational)](https://pharo.org) +[![Pharo 10](https://img.shields.io/badge/Pharo-10-informational)](https://pharo.org) +[![Pharo 11](https://img.shields.io/badge/Pharo-11-informational)](https://pharo.org) -This code was defined in 1998 as one of the traditional IETF April Fools' jokes, in RFC 2324, Hyper Text Coffee Pot Control Protocol. The RFC specifies this code should be returned by tea pots requested to brew coffee. +> *Name origin*: [418 I'm a teapot](http://en.wikipedia.org/wiki/List_of_HTTP_status_codes) +> (RFC 2324) is an HTTP status code. + +This code was defined in 1998 as one of the traditional IETF April Fools' jokes, +in RFC 2324, Hyper Text Coffee Pot Control Protocol. The RFC specifies this code +should be returned by tea pots requested to brew coffee. ## License + - The code is licensed under [MIT](LICENSE). - The documentation is licensed under [CC BY-SA 4.0](http://creativecommons.org/licenses/by-sa/4.0/). ## Quick Start -- Download the latest [Pharo 32](https://get.pharo.org/) or [64 bits VM](https://get.pharo.org/64/). +- Download the latest [Pharo 64 bits VM](https://get.pharo.org/64/). - Download a ready to use image from the [release page](http://github.com/zeroflag/Teapot/releases/latest) - Explore the [documentation](docs/). -``` +```smalltalk Metacello new - baseline: 'Teapot'; - repository: 'github://zeroflag/Teapot/source'; - load. + baseline: 'Teapot'; + repository: 'github://zeroflag/Teapot/source'; + load. ``` - ## Installation -To load the project in a Pharo image, or declare it as a dependency of your own project follow this [instructions](docs/Installation.md) +To load the project in a Pharo image, or declare it as a dependency of your own +project follow this [instructions](docs/Installation.md) ## Contributing @@ -39,4 +52,6 @@ Check the [Contribution Guidelines](CONTRIBUTING.md) ## Other -If you want to lively work with Teapot or quickly implement REST services with it we recommend to have a look at the [Tealight project](https://github.com/astares/Tealight) - a thin layer on top of Teapot to quickly experiment and deliver +If you want to lively work with Teapot or quickly implement REST services with +it, we recommend having a look at the [Tealight project](https://github.com/astares/Tealight) +a thin layer on top of Teapot to quickly experiment and deliver diff --git a/docs/Installation.md b/docs/Installation.md index 6e16f67..8dc2211 100644 --- a/docs/Installation.md +++ b/docs/Installation.md @@ -3,40 +3,44 @@ ## Basic Installation You can load **Teapot** evaluating: + ```smalltalk Metacello new - baseline: 'Teapot'; - repository: 'github://zeroflag/teapot:master/source'; - load. + baseline: 'Teapot'; + repository: 'github://zeroflag/teapot:master/source'; + load. ``` -> Change `master` to some released version if you want a pinned version + +> Change `master` to some released version if you want a pinned version ## Using as dependency -In order to include **Teapot** as part of your project, you should reference the package in your product baseline: +In order to include **Teapot** as part of your project, you should reference the +package in your product baseline: ```smalltalk setUpDependencies: spec - spec - baseline: 'Teapot' - with: [ spec - repository: 'github://zeroflag/Teapot:v{XX}/source'; - loads: #('Deployment') ]; - import: 'Teapot'. + spec + baseline: 'Teapot' + with: [ spec + repository: 'github://zeroflag/Teapot:v{XX}/source'; + loads: #('Deployment') ]; + import: 'Teapot'. ``` + > Replace `{XX}` with the version you want to depend on ```smalltalk baseline: spec - - spec - for: #common - do: [ self setUpDependencies: spec. - spec package: 'My-Package' with: [ spec requires: #('Teapot') ] ] + baseline> + spec + for: #common + do: [ self setUpDependencies: spec. + spec package: 'My-Package' with: [ spec requires: #('Teapot') ] ] ``` ## For Pharo 5 and previous -If you are using a Pharo older version see [Smalltalkhub](http://smalltalkhub.com/#!/~zeroflag/Teapot) +If you are using an older Pharo version see [Smalltalkhub](http://smalltalkhub.com/#!/~zeroflag/Teapot) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 499d6aa..16beebc 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -2,15 +2,15 @@ After installing you're ready to go -```Smalltalk +```smalltalk Teapot on GET: '/welcome' -> 'Hello World!'; start. ``` -Do it and view at: http://localhost:1701/welcome +`Do it` and view [here](http://localhost:1701/welcome) -# User's guide +## User's guide The most important concept of Teapot is the Route. @@ -20,11 +20,12 @@ An example of a Route definition is: A route has three parts: - - HTTP method (GET, POST, PUT, DELETE, HEAD, TRACE, CONNECT, OPTIONS, PATCH or any) - - URL pattern (/hi, /users/, /foo/*/bar/*, or a regexp) - - Action (block, message send or any object) +- HTTP method (`GET`, `POST`, `PUT`, `DELETE`, `HEAD`, `TRACE`, `CONNECT`, + `OPTIONS`, `PATCH` or any) +- URL pattern (`/hi`, `/users/`, `/foo/*/bar/*`, or a regexp) +- Action (block, message send or any object) -```Smalltalk +```smalltalk Teapot on GET: '/hi' -> 'Bonjour!'; GET: '/hi/' -> [:req | 'Hello ', (req at: #user)]; @@ -34,21 +35,25 @@ Teapot on (ZnEasy get: 'http://localhost:1701/hi/user1') entity string. "Hello user1" ``` -The Action part takes the HTTP request (optionally) and returns the response. The response may undergo further transformations by a response transformer that will constructs the final HTTP response (ZnResponse). +The Action part takes the HTTP request (optionally) and returns the response. +The response may undergo further transformations by a response transformer that +will construct the final HTTP response (`ZnResponse`). -``` +```smalltalk ZnRequest ⇨ [Router] ⇨ TeaRequest ⇨ [Route] ⇨ response ⇨ [Resp.Transformer] ⇨ ZnResponse ``` The response returned by the Action can be: -- Any Object that will be transformed by the given response transformer (e.g. html, ston, json, mustache, stream) to a HTTP response (ZnResponse). -- A TeaResponse that allows additional parameters to be added (response code, headers). -- A ZnResponse that will be handled directly by the ZnServer without further transformation. +- Any Object that will be transformed by the given response transformer (e.g. + html, ston, json, mustache, stream) to an HTTP response (`ZnResponse`). +- A `TeaResponse` that allows additional parameters to be added (response code, headers). +- A `ZnResponse` that will be handled directly by the `ZnServer` without further + transformation. The following 3 Routes produce the same output. -```Smalltalk +```smalltalk GET: '/greet' -> [:req | 'Hello World!' ] GET: '/greet' -> [:req | TeaResponse ok body: 'Hello World!' ] GET: '/greet' -> [:req | @@ -60,31 +65,47 @@ GET: '/greet' -> [:req | ## How Routes are matched -The Routes are matched in the order they are defined. The first route that matches the request method and the URL is invoked. If a Route matches but it returns 404, the search will continue. If no Route matches, 404 is returned. If a Route was invoked, its return value will be transformed to a HTTP response. If a Route returns a ZnResponse, no transformation will be performed. The default response transformer is a HTML one, so if you return a String, it will be written to the response with text/html content-type. If you use a Dictionary for example as return value and json as response transformer, then the output will be a json object, created from the Dictionary. +The Routes are matched in the order they are defined. The first route that matches +the request method and the URL is invoked. If a Route matches, but it returns 404, +the search will continue. If no Route matches, 404 is returned. If a Route was invoked, +its return value will be transformed to an HTTP response. If a Route returns a `ZnResponse`, +no transformation will be performed. The default response transformer is an HTML +one, so if you return a `String`, it will be written to the response with text/html +content-type. If you use a Dictionary for example as return value and JSON as response +transformer, then the output will be a JSON object, created from the `Dictionary`. -The URL pattern may contain named parameters (e.g. ), whose values accessible via the request object. The request is an extension of ZnRequest with some extra methods. A wildcard character (*) matches to one URL path segment. A wildcard terminated pattern is a greedy match; for example, '/foo/*' matches to '/foo/bar' and '/foo/bar/baz' too. +The URL pattern may contain named parameters (e.g. ``), whose values +are accessible via the request object. The request is an extension of `ZnRequest` +with some extra methods. A wildcard character `(*)` matches to one URL path segment. +A wildcard terminated pattern is a greedy match; for example, `'/foo/*'` matches +to `'/foo/bar'` and `'/foo/bar/baz'` too. -Query parameters and Form parameters can be accessed the same way as path parameters (req at: #paramName). +Query parameters and Form parameters can be accessed the same way as path parameters +`(req at: #paramName)`. ## Parameter constraints -```Smalltalk +```smalltalk Teapot on GET: '/user/' -> [:req | users findById: (req at: #id)]; output: #ston; start. ``` -- IsInteger matches digits (negative or positive) only and converts the value to an Integer -- IsNumber matches any integer or floating point number and converts the value to a Number +- `IsInteger` matches digits (negative or positive) only and converts the value + to an Integer +- `IsNumber` matches any integer or floating point number and converts the value + to a Number -See IsObject, IsInteger and IsNumber classes for information about introducing user defined constraints. +See `IsObject`, `IsInteger` and `IsNumber` classes for information about introducing +user defined constraints. ## Response transformers -The responsibility of a response transformer is to convert the output of the action block and set the content-type of the response. +The responsibility of a response transformer is to convert the output of the +action block and set the content-type of the response. -```Smalltalk +```smalltalk Teapot on GET: '/jsonlist' -> #(1 2 3 4); output: #json; GET: '/sometext' -> 'this is text plain'; output: #text; @@ -98,15 +119,15 @@ ZnEasy get: 'http://localhost:1701/download' "a ZnResponse(200 OK application/octet-stream 35B)" ``` -The default output is TeaOutput html that interprets the output as string, and sets the content-type to text/html. - -Some response transformers require external packages (e.g. NeoJSON, STON, Mustache) . See TeaOutput class for more information. +The default output is `TeaOutput html` that interprets the output as string, and +sets the `content-type` to `text/html`. -TODO explain how to write custom resp.transformer +Some response transformers require external packages (e.g. NeoJSON, STON, Mustache). +See `TeaOutput` class for more information. ## Templates -```Smalltalk +```smalltalk Teapot on GET: '/greet' -> {'phrase' -> 'Hello'. 'name' -> 'World'}; output: (TeaOutput mustacheHtml: '{{phrase}} {{name}}!'); @@ -115,9 +136,11 @@ Teapot on ## Aborts -An abort: message sent to the request object immediately stops a request (by signaling an exception) within a before filter or route. The same rules apply to the argument to the abort: message as the return value of a Route. +An abort: message sent to the request object immediately stops a request (by +signaling an exception) within a before filter or route. The same rules apply to +the argument to the abort: message as the return value of a Route. -```Smalltalk +```smalltalk Teapot on GET: '/secure/*' -> [:req | req abort: TeaResponse unauthorized]; GET: '/unauthorized' -> [:req | req abort: 'go away' ]; @@ -126,7 +149,7 @@ Teapot on ## Before filters -```Smalltalk +```smalltalk Teapot on before: '/secure/*' -> [:req | req session @@ -141,9 +164,10 @@ Before filters are evaluated before each request that matches the given URL patt ## After filters -After filters are evaluated after each request and can read the request and modify the response. +After filters are evaluated after each request and can read the request and modify +the response. -```Smalltalk +```smalltalk Teapot on after: '/*' -> [:req :resp | resp headers at: 'X-Foo' put: 'set by after filter']; start. @@ -151,7 +175,7 @@ Teapot on ## Serving static content -```Smalltalk +```smalltalk Teapot on serveStatic: '/statics' from: '/var/www/htdocs'; start. @@ -159,7 +183,7 @@ Teapot on ## Regex patterns -```Smalltalk +```smalltalk Teapot on GET: '/hi/([a-z]+\d\d)' asRegex -> [:req | 'Hello ', (req at: 1)]; start. @@ -168,13 +192,14 @@ Teapot on ZnEasy get: 'http://localhost:1701/hi/user'. "not found" ``` -Instead of < and > surrounded named parameters, the regexp pattern may contain subexpressions between parentheses whose values are accessible via the request object. +Instead of `<` and `>` surrounded named parameters, the regexp pattern may contain +sub-expressions between parentheses whose values are accessible via the request object. ### Error handlers To handle exceptions of a configured type(s) for all routes and before filters. -```Smalltalk +```smalltalk Teapot on GET: '/divide//' -> [:req | (req at: #a) / (req at: #b)]; GET: '/at/' -> [:req | dict at: (req at: #key)]; @@ -186,15 +211,17 @@ Teapot on (ZnEasy get: 'http://localhost:1701/div/6/0'). "bad request" ``` -You can use a comma-separated exception set to handle multiple exceptions. E.g. exception: ZeroDivide, DomainError -> handler. +You can use a comma-separated exception set to handle multiple exceptions. E.g. +`exception: ZeroDivide, DomainError -> handler`. -The same rules apply for the return values of the exception handler as were used for the Routes. +The same rules apply for the return values of the exception handler as were used +for the Routes. ## Query parameters Routes may also use query parameters: -```Smalltalk +```smalltalk Teapot on GET: '/books' -> [:req | books @@ -206,13 +233,16 @@ Teapot on "matches: http://localhost:1701/books?title=smalltalk&limit=12" ``` -This matches to GET http://localhost:1701/books?title=smalltalk&limit=12. Query parameters are optional to the /books route. You can use at:ifAbsent: to handle unset parameters. +This matches to `GET http://localhost:1701/books?title=smalltalk&limit=12`. +Query parameters are optional to the /books route. You can use `at:ifAbsent:` to +handle unset parameters. ## Conditions -Routes and Before/After filters may include conditions. A condition can be any expression that returns a Boolean. +Routes and Before/After filters may include conditions. A condition can be any +expression that returns a Boolean. -```Smalltalk +```smalltalk Teapot on GET: 'test1' -> result; when: [:req | req accept = 'application/json']; any: 'test2' -> result; when: [:req | #(GET POST) includes: req method]; @@ -222,11 +252,11 @@ Teapot on "second one matches if the request method is either GET or POST" ``` -## Multiple url patterns +## Multiple URL patterns -Teapot supports multiple url patterns per routes. +Teapot supports multiple URL patterns per routes. -```Smalltalk +```smalltalk Teapot on before: { '/secure/*' . '/protected/*' } -> [ :req | req abort: TeaResponse unauthorized ]; @@ -236,11 +266,14 @@ Teapot on ## Handling POST and other methods -Using POST/PUT and other HTTP methods is no different than using GET. In case of a POST the request represents the url encoded form data or whatever was posted. The request object has a generic at: method that can be used to access the path, query or form parameters in a uniform way. +Using POST/PUT and other HTTP methods is no different from using GET. In case of +a POST the request represents the URL encoded form data or whatever was posted. +The request object has a generic at: method that can be used to access the path, +query or form parameters in a uniform way. For example: -```Smalltalk +```smalltalk Teapot on GET: '/login' -> ' @@ -256,7 +289,7 @@ Teapot on REST example, showing some CRUD operations -```Smalltalk +```smalltalk books := Dictionary new. teapot := Teapot configure: { #defaultOutput -> #json. @@ -278,7 +311,7 @@ teapot Creating a book with the client. -```Smalltalk +```smalltalk ZnClient new url: 'http://localhost:8080/books/1'; formAt: 'author' put: 'SquareBracketAssociates'; @@ -293,7 +326,10 @@ ZnClient new ## Differences between Teapot and other web frameworks -- Teapot is not a singleton and doesn't hold any global state. You can run multiple Teapot servers inside the same image with isolated state. -- There are no thread locals or dynamic scoped variables in Teapot. Everything is explicit. +- Teapot is not a singleton and doesn't hold any global state. You can run multiple + Teapot servers inside the same image with isolated state. +- There are no thread locals or dynamic scoped variables in Teapot. Everything is + explicit. - It doesn't rely on annotations or pragmas, you can define the routes programmatically. -- It doesn't instantiate objects (e.g. "web controllers") for you. You can hook http events to existing objects, and manage their dependencies the way you want. +- It doesn't instantiate objects (e.g. "web controllers") for you. You can hook + http events to existing objects, and manage their dependencies the way you want. From 49c0059e79493c04de2b5329c5ba05f9f4bd251e Mon Sep 17 00:00:00 2001 From: Gabriel Omar Cotelli Date: Fri, 19 May 2023 16:06:11 -0300 Subject: [PATCH 2/6] Improve programming language detection --- .gitattributes | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..04b4f49 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +*.st linguist-language=Smalltalk +*.st eol=lf +*.st text diff From b2c4ff102d1f2aeb51b91f3a66c93ad91af7c1e9 Mon Sep 17 00:00:00 2001 From: Gabriel Omar Cotelli Date: Fri, 19 May 2023 16:07:54 -0300 Subject: [PATCH 3/6] Don't fail fast the build --- .github/workflows/unit-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index d4fd830..f1c4845 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -6,6 +6,7 @@ jobs: build: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: smalltalk: [ Pharo64-11, Pharo64-10, Pharo64-9.0, Pharo64-8.0, Pharo64-7.0 ] name: ${{ matrix.smalltalk }} From 31d5775b52bd38fa6122d4a279df13b5e85f2dc4 Mon Sep 17 00:00:00 2001 From: Gabriel Omar Cotelli Date: Fri, 19 May 2023 16:30:23 -0300 Subject: [PATCH 4/6] Update Baseline to load missing Zinc package in Pharo 10, 11 & 12 --- .../BaselineOfTeapot.class.st | 44 +++++++++++-------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/source/BaselineOfTeapot/BaselineOfTeapot.class.st b/source/BaselineOfTeapot/BaselineOfTeapot.class.st index 3c91866..6531e22 100644 --- a/source/BaselineOfTeapot/BaselineOfTeapot.class.st +++ b/source/BaselineOfTeapot/BaselineOfTeapot.class.st @@ -8,30 +8,38 @@ Class { BaselineOfTeapot >> baseline: spec [ - spec - for: #common - do: [ - self setUpDependencies: spec. - - spec - package: 'Teapot-Core' with: [ spec requires: #('NeoJSON') ]; - package: 'Teapot-Tests' with: [ spec requires: 'Teapot-Core' ]; - package: 'Teapot-Tools' with: [ spec requires: 'Teapot-Core' ]. + spec for: #common do: [ + self setUpDependencies: spec. - spec - group: 'Tests' with: #('Teapot-Tests'); - group: 'Tools' with: #('Teapot-Tools'); - group: 'Deployment' with: #('Teapot-Core'); - group: 'Development' with: #('Tests' 'Tools'); - group: 'default' with: 'Development' ] + spec + package: 'Teapot-Core' with: [ spec requires: #( 'NeoJSON' ) ]; + package: 'Teapot-Tools' with: [ spec requires: 'Teapot-Core' ]. + spec + for: #( #'pharo7.x' #'pharo8.x' #'pharo9.x' ) + do: [ spec package: 'Teapot-Tests' with: [ spec requires: 'Teapot-Core' ] ]. + + spec + for: #( #'pharo10.x' #'pharo11.x' #'pharo12.x' ) + do: [ spec package: 'Teapot-Tests' with: [ spec requires: #( 'Teapot-Core' 'Zinc-Zodiac' ) ] ]. + + spec + group: 'Tests' with: #( 'Teapot-Tests' ); + group: 'Tools' with: #( 'Teapot-Tools' ); + group: 'Deployment' with: #( 'Teapot-Core' ); + group: 'Development' with: #( 'Tests' 'Tools' ); + group: 'default' with: 'Development' + ] ] { #category : #baselines } BaselineOfTeapot >> setUpDependencies: spec [ spec - baseline: 'NeoJSON' - with: [ spec + baseline: 'NeoJSON' with: [ + spec repository: 'github://svenvc/NeoJSON:master/repository'; - loads: #('core') ] + loads: #( 'core' ) + ]; + baseline: 'Zinc' with: [ spec repository: 'github://svenvc/Zinc:master' ]; + project: 'Zinc-Zodiac' copyFrom: 'Zinc' with: [ spec loads: 'Zinc-Zodiac-Core' ] ] From b6774e5ad80aa9d617ebc84d328942d4b3f87061 Mon Sep 17 00:00:00 2001 From: Gabriel Omar Cotelli Date: Fri, 19 May 2023 16:32:34 -0300 Subject: [PATCH 5/6] Fix Zinc baseline name --- source/BaselineOfTeapot/BaselineOfTeapot.class.st | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/BaselineOfTeapot/BaselineOfTeapot.class.st b/source/BaselineOfTeapot/BaselineOfTeapot.class.st index 6531e22..e7f806b 100644 --- a/source/BaselineOfTeapot/BaselineOfTeapot.class.st +++ b/source/BaselineOfTeapot/BaselineOfTeapot.class.st @@ -37,9 +37,9 @@ BaselineOfTeapot >> setUpDependencies: spec [ spec baseline: 'NeoJSON' with: [ spec - repository: 'github://svenvc/NeoJSON:master/repository'; + repository: 'github://svenvc/NeoJSON'; loads: #( 'core' ) ]; - baseline: 'Zinc' with: [ spec repository: 'github://svenvc/Zinc:master' ]; + baseline: 'ZincHTTPComponents' with: [ spec repository: 'github://svenvc/zinc' ]; project: 'Zinc-Zodiac' copyFrom: 'Zinc' with: [ spec loads: 'Zinc-Zodiac-Core' ] ] From bad61dbeb0b956710196eb824c2e4f1471ec796e Mon Sep 17 00:00:00 2001 From: Gabriel Omar Cotelli Date: Fri, 19 May 2023 16:34:07 -0300 Subject: [PATCH 6/6] Fix baseline reference --- source/BaselineOfTeapot/BaselineOfTeapot.class.st | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/BaselineOfTeapot/BaselineOfTeapot.class.st b/source/BaselineOfTeapot/BaselineOfTeapot.class.st index e7f806b..b2f767d 100644 --- a/source/BaselineOfTeapot/BaselineOfTeapot.class.st +++ b/source/BaselineOfTeapot/BaselineOfTeapot.class.st @@ -41,5 +41,5 @@ BaselineOfTeapot >> setUpDependencies: spec [ loads: #( 'core' ) ]; baseline: 'ZincHTTPComponents' with: [ spec repository: 'github://svenvc/zinc' ]; - project: 'Zinc-Zodiac' copyFrom: 'Zinc' with: [ spec loads: 'Zinc-Zodiac-Core' ] + project: 'Zinc-Zodiac' copyFrom: 'ZincHTTPComponents' with: [ spec loads: 'Zinc-Zodiac-Core' ] ]