From 5b3bad4e1156e064a03d256d8d91852c1648f429 Mon Sep 17 00:00:00 2001 From: Lari Taskula Date: Mon, 20 Jun 2016 13:08:46 +0300 Subject: [PATCH] Bug 16212: Automatically minify swagger.json This patch adds automatic minification of swagger.json. Since startup subroutine is executed on each call to REST API, we need to be careful to not do useless computation by minificating swagger.json when not required. Therefore, we need some algorithm to determine whether minification is needed. In this patch, minification is executed in two cases: 1. swagger.min.json does not exist 2. any of the specification files is modified more recently than swagger.min.json Includes tests. This feature requires Apache user to have write permissions to swagger.min.json, otherwise we cannot minify the specification automatically. Another solution is to run the minifySwagger.pl manually after each modification or create a daemon that listens to file modifications via "inotify" and executes minification when needed. --- Koha/REST/V1.pm | 48 +++++++++++++++++ t/api/v1/minifier.t | 147 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 t/api/v1/minifier.t diff --git a/Koha/REST/V1.pm b/Koha/REST/V1.pm index 1bfda46..aaafaf2 100644 --- a/Koha/REST/V1.pm +++ b/Koha/REST/V1.pm @@ -18,8 +18,11 @@ package Koha::REST::V1; use Modern::Perl; use Mojo::Base 'Mojolicious'; +use File::Find::Rule; + use C4::Auth qw( check_cookie_auth get_session ); use C4::Context; +use Koha::Logger; use Koha::Patrons; sub startup { @@ -43,6 +46,8 @@ sub startup { # Force charset=utf8 in Content-Type header for JSON responses $self->types->type(json => 'application/json; charset=utf8'); + $self->minifySwagger(); + my $secret_passphrase = C4::Context->config('api_secret_passphrase'); if ($secret_passphrase) { $self->secrets([$secret_passphrase]); @@ -54,4 +59,47 @@ sub startup { }); } +sub minifySwagger { + my ($self, $swaggerPath) = @_; + + $swaggerPath = C4::Context->config("intranetdir").'/api/v1/' unless $swaggerPath; + my $pathToMinifier = $swaggerPath.'minifySwagger.pl'; + my $pathToSwaggerJson = $swaggerPath.'swagger.json'; + my $pathToSwaggerMinJson = $swaggerPath.'swagger.min.json'; + + if (_isMinificationRequired($swaggerPath, $pathToSwaggerJson, + $pathToSwaggerMinJson)) { + if (-e $pathToSwaggerMinJson && not -w $pathToSwaggerMinJson + || not -e $pathToSwaggerMinJson && not -w $swaggerPath) { + my $user = $ENV{LOGNAME} || $ENV{USER} || getpwuid($<); + my $logger = Koha::Logger->get({ interface => 'intranet' }); + $logger->warn("User $user cannot write to $pathToSwaggerMinJson. ". + "Please give $user write-permission to $pathToSwaggerMinJson ". + "or run $pathToMinifier manually."); + return; + } + my $output = `perl $pathToMinifier -s $pathToSwaggerJson -d $pathToSwaggerMinJson`; + if ($output) { + die $output; + } + } +} + +sub _isMinificationRequired { + my ($swaggerPath, $pathToSwaggerJson, $pathToSwaggerMinJson) = @_; + + return 1 unless -e $pathToSwaggerJson; # swagger.json exists? + return 1 unless -e $pathToSwaggerMinJson; # swagger.min.json exists? + my $latestModification = -C $pathToSwaggerJson; + + # Recursively go through specification files and find the last modified + my $lastModifiedFile; + my @files = File::Find::Rule->file()->name("*.json")->in($swaggerPath); + foreach my $file (@files){ + next if $file eq $pathToSwaggerMinJson; + $latestModification = -C $file if -C $file < $latestModification; + } + return 1 if $latestModification < -C $pathToSwaggerMinJson; # compare modification time +} + 1; diff --git a/t/api/v1/minifier.t b/t/api/v1/minifier.t new file mode 100644 index 0000000..881aa87 --- /dev/null +++ b/t/api/v1/minifier.t @@ -0,0 +1,147 @@ +#!/usr/bin/env perl + +# Copyright (C) 2016 KohaSuomi +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. +# +# Koha is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with Koha; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +use Modern::Perl; + +use File::Copy qw/copy/; +use File::Temp; +use File::Path qw/make_path rmtree/; + +use Test::More tests => 13; + +use C4::Context; +use Koha::REST::V1; + +my $oldSwagger_title = "Swagger Sample App"; +my $newSwagger_title = "Koha REST API"; + +# Construct an example swagger.json +my $swaggerJsonTest = +qq ({ + "swagger": "2.0", + "info": { + "title": "$oldSwagger_title", + "version": "1" + }, + "paths": {}, + "definitions": {}, + "parameters": {} +}); + +my $swaggerJsonTestModified = +qq ({ + "swagger": "2.0", + "info": { + "title": "$newSwagger_title", + "version": "1" + }, + "paths": {}, + "definitions": { + "\$ref": "definitions.json" + }, + "parameters": {} +}); +my $swaggerJsonTestDefinitions = +qq ({ + "test": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "test integer" + } + } + } +}); + +# Path containing actual swagger specification files +my $real_swaggerPath = C4::Context->config("intranetdir").'/api/v1/'; + +# Create a tmp directory where we can modify swagger.json +my $swaggerPath = File::Temp->newdir()."/"; +make_path($swaggerPath); + +# Copy minifySwagger.pl to this location +copy($real_swaggerPath.'minifySwagger.pl', $swaggerPath); + +my $pathToMinifier = $swaggerPath.'minifySwagger.pl'; +my $pathToSwaggerJson = $swaggerPath.'swagger.json'; +my $pathToSwaggerDefinitionsJson = $swaggerPath.'definitions.json'; +my $pathToSwaggerMinJson = $swaggerPath.'swagger.min.json'; + +is(-e $pathToSwaggerJson, undef, "swagger.json does not yet exist"); +is(-e $pathToSwaggerMinJson, undef, "swagger.min.json does not yet exist"); +is(-e $pathToSwaggerDefinitionsJson, undef, "definitions.json does not yet exist"); +ok(-w $swaggerPath, "$swaggerPath is writeable"); + +my $exists = -e $pathToSwaggerJson; +# Create swagger.json test file +open my $fh, '>', $pathToSwaggerJson; +print $fh $swaggerJsonTest; +close $fh; + +ok(-e $pathToSwaggerJson, "swagger.json created!"); + +Koha::REST::V1::minifySwagger(undef, $swaggerPath); +ok(-e $pathToSwaggerMinJson, "swagger.min.json created!"); +my $lastmodified = -C $pathToSwaggerMinJson; + +# Read swagger.min.json +require Swagger2; +my $swaggerMin = Swagger2->new($pathToSwaggerMinJson); +$swaggerMin = $swaggerMin->expand; +my $oldSwaggerTitle = $swaggerMin->{'api_spec'}->{'data'}->{'info'}->{'title'}; +is($oldSwaggerTitle, $oldSwagger_title, "old title found"); + +# Modify swagger.json +sleep(1); # make sure modification time differs from swagger.min.json +open $fh, '>', $pathToSwaggerJson; +print $fh $swaggerJsonTestModified; +close $fh; + +# Add definitions.json +sleep(1); # make sure modification time differs from swagger.min.json +open $fh, '>', $pathToSwaggerDefinitionsJson; +print $fh $swaggerJsonTestDefinitions; +close $fh; + +ok(-e $pathToSwaggerDefinitionsJson, "definitions.json created!"); + +Koha::REST::V1::minifySwagger(undef, $swaggerPath); + +ok($lastmodified > -C $pathToSwaggerMinJson, + "swagger.min.json modified after editing swagger.json"); +$lastmodified = -C $pathToSwaggerMinJson; + +# Re-read swagger.min.json +$swaggerMin = Swagger2->new($pathToSwaggerMinJson); +$swaggerMin = $swaggerMin->expand; +my $newSwaggerTitle = $swaggerMin->{'api_spec'}->{'data'}->{'info'}->{'title'}; +is($newSwaggerTitle, $newSwagger_title, "new title found"); # was minified +is($swaggerMin->{'api_spec'}->{'data'}->{'definitions'} + ->{'test'}->{'properties'}->{'id'}->{'description'}, + "test integer", "test integer found!"); + +Koha::REST::V1::minifySwagger(undef, $swaggerPath); +ok($lastmodified == -C $pathToSwaggerMinJson, + "swagger.min.json not modified because no modifications to swagger.json"); + +# Cleanup +rmtree($swaggerPath) if $swaggerPath ne $real_swaggerPath; +is(-e $swaggerPath, undef, "tmp files deleted"); -- 1.9.1