rest(n)

πŸ“¦
version 2.0 Β· 2 versions
, in fedora 43, fedora 44, nixos 26.05, nixpkgs unstable

🌸 NAME 🌸

rest β€” bestie, this is the whole framework for defining REST web APIs and calling them inline or asychronously πŸ’… (yes the manual says it like that, we don't question the ancestors)

It's the rest manual page, section n β€” Tcl-land, iykyk πŸ¦‹


✨ SYNOPSIS ✨

package require Tcl 8.5 9

package require rest ?1.7?

::rest::simple url query ?config? ?body?

::rest::get url query ?config? ?body?

::rest::post url query ?config? ?body?

::rest::patch url query ?config? ?body?

::rest::head url query ?config? ?body?

::rest::put url query ?config? ?body?

::rest::delete url query ?config? ?body?

::rest::save name file

::rest::describe name

::rest::parameters url ?key?

::rest::parse_opts static required optional words

::rest::substitute string var

::rest::create_interface name

πŸ’– DESCRIPTION πŸ’–

Okay so this package gives you two whole flavors of usage: simple calls and complete interfaces ✨

In an interface you specify a set of rules, and then the package literally builds the commands that correspond to the REST methods for you. Those commands can have so many options β€” input and output transformations, data-type-specific formatting, the whole wardrobe πŸ’… β€” and the result is a cleaner, simpler script. It's giving main character energy.

On the other hand, a simple call is easier and quicker to implement, but it's also less featureful πŸ™ˆ. It takes the url and a few options about the command and returns the result directly. Any formatting or checking is up to the rest of your script. No cap.


πŸŽ€ SIMPLE USAGE πŸŽ€

In simple usage you make calls using the http method procedures and then check or process the returned data yourself. You're on your own for the aftercare, bestie 🫢

::rest::simple url query ?config? ?body?

::rest::get url query ?config? ?body?

::rest::post url query ?config? ?body?

::rest::patch url query ?config? ?body?

::rest::head url query ?config? ?body?

::rest::put url query ?config? ?body?

::rest::delete url query ?config? ?body?

These commands are all equivalent except for the http method used πŸ‘‘ So they're the same girl, different outfit.

  • If you use simple then the method should be specified as an option in the config dictionary.
  • If that is not done it defaults to get.
  • If a body is needed then the config dictionary must be present β€” however it is allowed to be empty. Empty is still present, bestie, that's the rule ✨

The config dictionary supports the following keys:

  • auth
  • content-type
  • cookie
  • error-body
  • format
  • headers
  • method
  • timeout

Two quick examples, because we love a demo πŸ¦‹

Example 1, Yahoo Boss:

set appid APPID
set search tcl
set res [rest::get http://boss.yahooapis.com/ysearch/web/v1/$search [list appid $appid]]
set res [rest::format_json $res]

Example 2, Twitter:

set url   http://twitter.com/statuses/update.json
set query [list status $text]
set res [rest::simple $url $query {
    method post
    auth   {basic user password}
    format json
}]

🌈 INTERFACE USAGE 🌈

An interface to a REST API consists of a series of definitions of REST calls contained in an array πŸ“š The name of that array becomes a namespace containing the defined commands. Each key of the array specifies the name of the call, with the associated configuration a dictionary, i.e. key/value pairs. The acceptable keys, i.e. legal configuration options, are described below πŸ‘‡ After creating the definitions in the array, simply calling rest::create_interface with the array as argument will then create the desired commands. Manifesting ✨

Example, Yahoo Weather:

package require rest

set yweather(forecast) {
   url      http://weather.yahooapis.com/forecastrss
   req_args { p: }
   opt_args { u: }
}
rest::create_interface yweather
puts [yweather::forecast -p 94089]

πŸ’Ύ ::rest::save name file

This command saves a copy of the dynamically created procedures for all the API calls specified in the array variable name to the file, for later loading. Saving your work? Iconic behavior πŸ’…

The result of the command is the empty string.


πŸ“ ::rest::describe name

This command prints a description of all API calls specified in the array variable name to the channel stdout.

The result of the command is the empty string.


πŸ” ::rest::parameters url ?key?

This command parses an url query string into a dictionary and returns said dictionary as its result.

If key is specified the command will not return the entire dictionary, but only the value of that key. Just the one, bestie πŸŽ€


🧠 ::rest::parse_opts static required optional words

This command implements a custom parser for command options. She's very capable, let her cook πŸ‘‘

dict static

A dictionary of options and their values that are always present in the output. Always there. Loyal. 🫢

list required

A list of options that must be supplied by words.

list optional

A list of options that may appear in the words, but are not required. The elements must be in one of three forms:

  • name β€” The option may be present or not, no default.
  • name: β€” When present the option requires an argument.
  • name:value β€” When not present use value as default.

list words

The words to parse into options and values.


The result of the command is a list containing two elements:

  1. A dictionary containing the parsed options and their values.
  2. A list of the remaining words.

πŸͺ„ ::rest::substitute string var

This command takes a string, substitutes values for any option identifiers found inside, and returns the modified string as its result.

The values to substitute are found in the variable var, which is expected to contain a dictionary mapping from the option identifiers to replace to their values. πŸ“– Note that option identifiers which have no key in var are replaced with the empty string. Gone. Ghosted. πŸ’€

The option identifiers in string have to follow the syntax %...% where ... may contain any combination of lower-case alphanumeric characters, plus underscore, colon and dash. That's the whole alphabet of allowed vibes, fr fr.


πŸ‘‘ ::rest::create_interface name

This command creates procedures for all the API calls specified in the array variable name.

The name of that array becomes a namespace containing the defined commands. Each key of the array specifies the name of the call, with the associated configuration a dictionary, i.e. key/value pairs. The legal keys and their meanings are:

  • url β€” The value of this required option must be the target of the http request.
  • description β€” The value of this option must be a short string describing the call. Defaults to the empty string, if not specified. Used only by ::rest::describe.
  • body β€” The value of this option indicates if arguments are required for the call's request body or not. The acceptable values are listed below. Defaults to optional if not specified.
    • none β€” The call has no request body, none must be supplied.
    • optional β€” A request body can be supplied, but is not required.
    • required β€” A request body must be supplied.
    • argument β€” This value must be followed by the name of an option, treating the entire string as a list. The request body will be used as the value of that option.
    • mime_multipart β€” A request body must be supplied and will be interpreted as each argument representing one part of a mime/multipart document. Arguments must be lists containing 2 elements, a list of header keys and values, and the mime part body, in this order. Order matters bestie, don't flip it πŸ™ˆ
    • mime_multipart/<value> β€” Same as mime_multipart, but the Content-Type header is set to multipart/<value>.
  • method β€” The value of this option must be the name of the HTTP method to call on the url. Defaults to GET, if not specified. The acceptable values are GET, POST, and PUT, regardless of letter-case. Capitalize her or don't, she doesn't care πŸ’…
  • copy β€” When present the value of this option specifies the name of a previously defined call. The definition of that call is copied to the current call, except for the options specified by the current call itself. Steal her look, but make it yours ✨
  • unset β€” When present the value of this option contains a list of options in the current call. These options are removed from the definition. Use this after copying an existing definition to remove options, instead of overriding their value. Basically the declutter of options 🧹
  • headers β€” Specification of additional header fields. The value of this option must be a dictionary, interpreted to contain the new header fields and their values. The default is to not add any additional headers.
  • content-type β€” The value of this option specifies the content type for the request data.
  • req_args β€” The value of this option is a list naming the required arguments of the call. Names ending in a colon will require a value. Colon = she needs a plus-one πŸŽ€
  • opt_args β€” The value of this option is a list naming the arguments that may be present for a call but are not required.
  • static_args β€” The value of this option is a list naming the arguments that are always the same. No sense in troubling the user with these. A leading dash (-) is allowed but not required, to maintain consistency with the command line.
  • auth β€” The value of this option specifies how to authenticate the calls. No authentication is done if the option is not specified.
  • basic β€” bestie, you set up basic auth by overriding the procedure basic_auth inside the namespace of your interface. It takes two arguments, in this exact order: the username, then the password. That's it. That's the whole thing. πŸ’…
  • bearer β€” you want a bearer token as your auth? Then the value is the token, and it gets passed to the HTTP authorization header. Simple. Clean. Iconic. ✨
  • sign β€” ok so this one's a lil different: the value has to actually be a list, and the second element of that list is the name of a procedure that gets called to do the request signing. Don't ask for more, that's what the page says. 🫢
  • callback β€” the moment this option shows up, the method gets created as an async call. Those calls return immediately with the value of the associated http token instead of the call's result. And heads up: the event loop must be active to use this option. No event loop, no callback, periodt.

The value of this option is treated as a command prefix, which gets invoked once the HTTP call is complete. That prefix receives at least two extra arguments, in this order: the name of the calling procedure, then the status of the result (one of OK or ERROR).

If the status is OK, a third argument gets added β€” the data associated with the result.

And if and only if the ERROR is a redirection, the location that got redirected to is added as an argument. On top of that, if the configuration key error-body is set to true, the data associated with the result gets added as an argument too.

Also, inside that procedure, the http request header is available via upvar token token. Slay. πŸ’–

  • cookie β€” the value here is a list of cookies to be passed in the http header. It's literally just a shortcut to the headers option, so you don't have to type as much. Laziness is a virtue, fr fr. πŸ“
  • input_transform β€” the value is a command prefix or script that transforms the query before the call gets invoked. If you hand it a script, it gets wrapped into an automatically generated internal procedure. No transformation option specified? Then no transformation happens, obviously.

The command (prefix) must accept a single argument β€” the query (a dictionary) it's transforming β€” and it must return the modified query (again, as a dictionary) as its result. Bonus: the request body is reachable inside the transform command via upvar body body. πŸ‘‘

  • format
  • result β€” the value of this option says what format the returned data is in. Defaults to auto if you don't specify anything. The acceptable values are:
    • auto β€” auto detects between xml and json.
    • discard
    • json
    • raw
    • rss β€” this one's formatted as a special case of xml.
    • tdom
    • xml
And yes, discard, json, raw, tdom and xml really do just sit there with no extra explanation on this page. That's all it gives us. Not me wanting more lore and getting none. 😭
  • pre_transform β€” the value is a command prefix or script that transforms the result of a call, and it runs before the output transform as per format gets applied. A script transform gets wrapped into an automatically generated internal procedure.

Not specified? No transformation. That's the vibe.

The command (prefix) must accept a single argument β€” the result to transform β€” and must return the modified result as its result.

The http request header is accessible in the transform command via upvar token token. πŸ’œ

  • post_transform β€” same energy as pre_transform, except this one runs after the output transform as per format gets applied. A script transform gets wrapped into an automatically generated internal procedure.

Not specified? You already know. No transformation.

The command (prefix) must accept a single argument β€” the result to transform β€” and must return the modified result as its result.

And again, the http request header is accessible in the transform command via upvar token token. 🧚

  • check_result β€” the value of this option must be a list of two expressions, and either of them may be empty. Here's the deal:
    • The first expression checks the OK condition. It must return true when the result is satisfactory, and false otherwise.
    • The second expression is the ERROR condition. It must return false unless there's an error β€” and then it has to return true.
  • error_body β€” the value of this option decides whether to return the response when you hit an HTTP error, or not. The default is to not return the response body on error. If you want the details on that, go read callback above. πŸ”₯
  • timeout β€” the value of this option decides whether a timeout gets set on the HTTP call. By default, no timeout is set. The timeout value is accepted in milliseconds. Tick tock. ⏰

πŸ¦‹ EXAMPLES πŸ¦‹

Yahoo Geo:

set ygeo(parse) {
    url http://wherein.yahooapis.com/v1/document
    method post
    body { arg documentContent }
}
ygeo::parse "san jose ca"
# "san jose ca" will be interpreted as if it were specified as the -documentContent option

Google Docs:

set gdocs(upload) {
    url http://docs.google.com/feeds/default/private/full
    body mime_multipart
}
gdocs::upload [list {Content-Type application/atom+xml} $xml] [list {Content-Type image/jpeg} $filedata]

Delicious:

set delicious(updated) {
    url https://api.del.icio.us/v1/posts/update
    auth basic
}

rest::create_interface flickr

flickr::basic_auth username password

Flickr:

set flickr(auth.getToken) {
   url http://api.flickr.com/services/rest/
   req_args { api_key: secret: }
   auth { sign do_signature }
}

rest::create_interface flickr

proc ::flickr::do_signature {query} {
    # perform some operations on the query here
    return $query
}

🌈 INCLUDED 🌈

The package ships functional but incomplete implementations for the following services β€” so like, they work, but don't expect the full fantasy:

  • del.icio.us
  • facebook
  • flickr
  • twitter
  • google calendar
  • yahoo boss
  • yahoo weather

Please either read the package's implementation, or use rest::describe after loading it, to get their details. And do not forget the developers' documentation on the respective sites either, bestie. πŸ‘‘


πŸ”’ TLS πŸ”’

The rest package can be used with https-secured services β€” you just need to require the TLS package and then register it with the http package that it's sitting on top of. Example:

package require tls
http::register https 443 ::tls::socket

πŸ›‘οΈ TLS SECURITY CONSIDERATIONS πŸ›‘οΈ

This package uses the TLS package to handle the security for https urls and other socket connections.

But here's the thing β€” policy decisions like which protocols to support and which ciphers to use are not the responsibility of TLS, and not the responsibility of this package either. Those decisions belong to whichever application is using the package, and they're probably influenced by the set of servers that application talks to as well.

For example: in light of the recent POODLE attack discovered by Google (http://googleonlinesecurity.blogspot.co.uk/2014/10/this-poodle-bites-exploiting-ssl-30.html), lots of servers will disable support for the SSLv3 protocol. To handle that change, the applications using TLS have to be patched β€” not this package, and not TLS itself. And such a patch may be as simple as generally activating tls1 support, like this:

package require tls
tls::init -tls1 1 ;# forcibly activate support for the TLS1 protocol

... your own application code ...

πŸ’€ BUGS, IDEAS, FEEDBACK πŸ’€

This document, and the package it describes, will undoubtedly contain bugs and other problems. (Manpage honesty, we love to see it.) Please report such in the category rest of the Tcllib Trackers: http://core.tcl.tk/tcllib/reportlist. Please also report any ideas for enhancements you may have for either the package and/or the documentation. We want the lore.

When proposing code changes, please provide unified diffs, i.e. the output of diff -u.

Note further that attachments are strongly preferred over inlined patches. You can make attachments by going to the Edit form of the ticket immediately after its creation, and then using the left-most button in the secondary navigation bar.


and that's a wrap on rest(n), bestie πŸ’– this page is from tcllib 1.7, and it's the rest(n) manual page itself β€” go forth, make HTTP calls, sign your requests, and always check your result. mwah πŸŽ€

πŸ“¦ where this page comes from