Athena 0.12.0

Athena 0.12.0

This release focused on polishing and improving existing core functionality. Highlights include a reworked parameter system, URL generation, and improved external documentation.

Website

As an ongoing effort to make Athena easier to use, I spent some time improving its documentation. Up until now everything has been documented within the generated API docs of each component, with some things here and there being more general. However, while the documentation for each component was quite good, there wasn’t anything to tie everything together.

In order to solve this, I created and launched a new website to include external Athena documentation. The website includes:

  • An introduction to the Athena Framework, and to Athena itself; including high level feature overview and links to various resources
  • An overview of how Athena is designed; following the flow that each request makes
    • Subsections talk about how the various components are integrated into Athena
  • A cookbook for sharing commonly useful types
  • The getting started has also been moved to the external documentation

Beta Website

In addition to the official website, a beta version has also been launched as a means to beta test unified documentation. I.e. external documentation + API documentation within the same website. The goal of the beta site is to gather feedback around the concept to allow for improvements to be made without affecting the usability/processes already in place. It also serves as an example of what could be the future for Crystal API documentation, outside of the Athena Framework.

Shoutout to @oprypin for his ongoing mkdocs plugin project that powers the API documentation generation.

Enhanced Parameters

One of the more exciting features of this release is the work done to improve the usability/functionality of parameters, namely ART::QueryParam. Previously, the @[ART::QueryParam] annotation did not actually do anything; nor did it offer any functionality other than serving as a marker that a given action uses one.

require "athena"

class ExampleController < ART::Controller
  # A `requirements` field allows valdiating the parameter's value against a Regex pattern.
  @[ART::Get("/regex")]
  @[ART::QueryParam("page", requirements: /\d{2}/)]
  def index(page : Int32) : Int32
    page
  end
  
  # An `AVD::Constraint` may also be used.
  @[ART::Get("/constraint")]
  @[ART::QueryParam("page", requirements: @[Assert::PositiveOrZero])]
  def index2(page : Int32) : Int32
    page
  end
  
  # Arguments that are nilable (or have a default value) are considered optional.
  @[ART::Get("/optional")]
  @[ART::QueryParam("page")]
  def index3(page : Int32?) : Int32?
    page
  end
end

ART.run

# GET /regex          # => {"code":422,"message":"Parameter 'page' of value '' violated a constraint: 'This value should not be null.'\n"}
# GET /regex?page=10  # => 10
# GET /regex?page=bar # => {"code":400,"message":"Required parameter 'page' with value 'bar' could not be converted into a valid 'Int32'."}
# GET /regex?page=5   # => {"code":422,"message":"Parameter 'page' of value '5' violated a constraint: 'Parameter 'page' value does not match requirements: (?-imsx:^(?-imsx:\\d{2})$)'\n"}

# GET /constraint?page=-5 # => {"code":422,"message":"Parameter 'page' of value '-9' violated a constraint: 'This value should be positive or zero.'\n"}

# GET /optional          # => null
# GET /optional?page=2   # => 2

Additional parameter arguments include:

  • incompatibles which represent parameters that cannot be present at the same time.
  • map allows providing arrays of values to a controller action, running validations against each individual value
  • converter parameters can now have dedicated ART::ParamConverterInterfaces

In addition to these improvements, this release also introduces a new parameter type: ART::RequestParam which represents form data within the request body.

require "athena"

class ExampleController < ART::Controller
  @[ART::Post(path: "/login")]
  @[ART::RequestParam("username")]
  @[ART::RequestParam("password")]
  def login(username : String, password : String) : Nil
    # ...
  end
end

ART.run

# POST /login, body: "username=George&password=abc123"

See the related API documentation for more information and examples.

URL Generation

Last but not least, Athena went through some internal refactors that enable some more advanced routing related features. The first of which is the ability to generate URLs for the provided route and passed in parameters. A helper method was also added to ART::Controller to make this easier. See the related API documentation for more information.

require "athena"

class ExampleController < ART::Controller
  @[ART::Get("/add/:value1/:value2", name: "add")]
  def add(value1 : Int32, value2 : Int32, negative : Bool = false) : Int32
    0
  end

  @[ART::Get("/")]
  def get_link : String
    self.generate_url "add", value1: 10, value2: 5 # => /add/10/5
    
    # Another helper method also exists to redirect to another route
    self.redirect_to_route "add", value1: 10, value2: 5
  end
end

Honorable Mentions

A list of things worth mentioning but not big enough to warrant their own section:

  • ART::Spec::APITestCase for integration testing controllers
  • Responses are now written directly all at once, use ART::StreamedResponse in order to stream the response
  • Include charset=UTF-8 within content-type header by default since strings are inherently UTF-8
  • A few miscellaneous bug fixes

Checkout the release notes for a complete list of changes. As usual feel free to join me in the Athena Gitter channel if you have any suggestions, questions, or ideas. I’m also available on Discord (Blacksmoke16#0016) or via Email.

8 Likes

Congrats on the release!

1 Like

I really like how this is coming together. I admit, when you first announced Athena, I didn’t understand where you were going with it beyond “it uses annotations”, and I didn’t pick up on why that was a good idea. But with each release it’s making a lot more sense.

It’s also pretty neat how you’re basically putting on a clinic for all of us with these Athena announcements showing how annotations can be used to make code more expressive — not just for things like DB::Field or JSON::Field like the rest of us are using them for. :joy:

2 Likes