├── .gitignore ├── .eslintrc ├── LICENSE.md ├── .babelrc ├── .editorconfig ├── webpack.config.babel.js ├── src └── index.js ├── README.md ├── package.json └── lib └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | *.log 4 | # lib 5 | dist 6 | es 7 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-airbnb", 3 | "env": { 4 | "mocha": true, 5 | "node": true 6 | }, 7 | "globals": { 8 | "expect": true 9 | }, 10 | "rules": { 11 | "padded-blocks": 0, 12 | "no-use-before-define": [2, "nofunc"], 13 | "no-unused-expressions": 0 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Dan Abramov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["transform-es2015-template-literals", { "loose": true }], 4 | "transform-es2015-literals", 5 | "transform-es2015-function-name", 6 | "transform-async-to-generator", 7 | "transform-es2015-arrow-functions", 8 | "transform-es2015-block-scoped-functions", 9 | ["transform-es2015-classes", { "loose": true }], 10 | "transform-es2015-object-super", 11 | "transform-es2015-shorthand-properties", 12 | ["transform-es2015-computed-properties", { "loose": true }], 13 | ["transform-es2015-for-of", { "loose": true }], 14 | "transform-es2015-sticky-regex", 15 | "transform-es2015-unicode-regex", 16 | "check-es2015-constants", 17 | ["transform-es2015-spread", { "loose": true }], 18 | "transform-es2015-parameters", 19 | ["transform-es2015-destructuring", { "loose": true }], 20 | "transform-es2015-block-scoping", 21 | "transform-es3-member-expression-literals", 22 | "transform-es3-property-literals" 23 | ], 24 | "presets": ["stage-2", "es2015"], 25 | "env": { 26 | "commonjs": { 27 | "plugins": [ 28 | ["transform-es2015-modules-commonjs", { "loose": true }] 29 | ] 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent coding styles between different editors and IDEs. 2 | # Requires EditorConfig JetBrains Plugin - http://github.com/editorconfig/editorconfig-jetbrains 3 | 4 | # Set this file as the topmost .editorconfig 5 | # (multiple files can be used, and are applied starting from current document location) 6 | root = true 7 | 8 | # Use bracketed regexp to target specific file types or file locations 9 | [*.{js,json}] 10 | 11 | # Use hard or soft tabs ["tab", "space"] 12 | indent_style = space 13 | 14 | # Size of a single indent [an integer, "tab"] 15 | indent_size = tab 16 | 17 | # Number of columns representing a tab character [an integer] 18 | tab_width = 2 19 | 20 | # Line breaks representation ["lf", "cr", "crlf"] 21 | end_of_line = lf 22 | 23 | # ["latin1", "utf-8", "utf-16be", "utf-16le"] 24 | charset = utf-8 25 | 26 | # Remove any whitespace characters preceding newline characters ["true", "false"] 27 | trim_trailing_whitespace = true 28 | 29 | # Ensure file ends with a newline when saving ["true", "false"] 30 | insert_final_newline = true 31 | 32 | # Markdown files 33 | [*.md] 34 | 35 | # Trailing whitespaces are significant in Markdown. 36 | trim_trailing_whitespace = false 37 | -------------------------------------------------------------------------------- /webpack.config.babel.js: -------------------------------------------------------------------------------- 1 | import webpack from 'webpack'; 2 | import path from 'path'; 3 | 4 | const { NODE_ENV } = process.env; 5 | 6 | const plugins = [ 7 | new webpack.optimize.OccurenceOrderPlugin(), 8 | new webpack.DefinePlugin({ 9 | 'process.env.NODE_ENV': JSON.stringify(NODE_ENV), 10 | }), 11 | ]; 12 | 13 | const filename = `redux-swagger-client${NODE_ENV !== 'development' ? '.min' : ''}.js`; 14 | 15 | NODE_ENV !== 'development' && plugins.push( 16 | new webpack.optimize.UglifyJsPlugin({ 17 | compressor: { 18 | pure_getters: true, 19 | unsafe: true, 20 | unsafe_comps: true, 21 | screw_ie8: true, 22 | warnings: false, 23 | }, 24 | }) 25 | ); 26 | 27 | export default { 28 | module: { 29 | loaders: [ 30 | { 31 | test: /\.jsx?$/, 32 | exclude: /node_modules/, 33 | loader: 'babel-loader', 34 | query: { 35 | presets: ['stage-2', 'es2015'], 36 | }, 37 | }, 38 | ], 39 | }, 40 | // module: { 41 | // loaders: [ 42 | // { test: /\.js$/, loaders: ['babel-loader'], exclude: /node_modules/ }, 43 | // ], 44 | // }, 45 | 46 | entry: [ 47 | './src/index', 48 | ], 49 | 50 | output: { 51 | path: path.join(__dirname, 'dist'), 52 | filename, 53 | library: 'ReduxSwaggerClient', 54 | libraryTarget: 'umd', 55 | }, 56 | 57 | plugins, 58 | }; 59 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Swagger from 'swagger-client'; 2 | 3 | export default function swaggerMiddleware(opts) { 4 | return ({ dispatch, getState }) => next => action => { 5 | if (typeof action === 'function') { 6 | return action(dispatch, getState); 7 | } 8 | 9 | if (!action.swagger) { 10 | return next(action); 11 | } 12 | 13 | return new Swagger({ 14 | ...opts, 15 | requestInterceptor(req) { 16 | return !!opts.requestInterceptor 17 | ? opts.requestInterceptor(req, action, dispatch, getState) 18 | : req; 19 | }, 20 | responseInterceptor(resp) { 21 | return !!opts.responseInterceptor 22 | ? opts.responseInterceptor(resp, action, dispatch, getState) 23 | : resp; 24 | } 25 | }) 26 | .then(result => { 27 | const { swagger, types, actions, ...rest } = action; 28 | const [REQUEST, SUCCESS, FAILURE] = actions || types; 29 | const callApi = async (apis, sw) => { 30 | if (typeof sw !== 'function') { 31 | const error = new Error('Swagger api call is not a function'); 32 | opts.error && opts.error(error); 33 | throw error; 34 | } 35 | if(typeof REQUEST === 'function') next(REQUEST(rest)); 36 | else if(REQUEST) next({ ...rest, type: REQUEST }); 37 | !!opts.preRequest && await opts.preRequest(action); 38 | try { 39 | const resp = await sw(apis, action); 40 | if(typeof SUCCESS === 'function') { 41 | return next(SUCCESS({...rest, result: resp})); 42 | } else if(SUCCESS !== undefined) { 43 | return next({ ...rest, result: resp, type: SUCCESS }); 44 | } 45 | } catch (error) { 46 | if(typeof FAILURE === 'function') { 47 | return next(FAILURE({...rest, error})); 48 | } else if(FAILURE !== undefined) { 49 | return next({ ...rest, error, type: FAILURE }); 50 | } 51 | } 52 | }; 53 | 54 | return callApi(result.apis, swagger); 55 | }, 56 | err => opts.error && opts.error(err) 57 | ).catch(err => opts.error && opts.error(err)); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Redux Swagger Client 2 | ==================== 3 | 4 | Swagger middleware for redux 5 | 6 | ## About 7 | This is an attempt to add asynchronous swagger api calls to redux. It works by dispatching an action that includes the field `swagger` that takes a function and passes the swagger client element to that function. If the swagger spec has not yet been parsed, the action will get queued. 8 | 9 | ## Installation 10 | note: This module requires [redux-thunk](https://github.com/gaearon/redux-thunk) 11 | Github: 12 | ``` 13 | npm install --save github:noh4ck/redux-swagger-client 14 | ``` 15 | 16 | Package pending: 17 | ``` 18 | npm install --save redux-swagger-client 19 | ``` 20 | 21 | To enable Redux Swagger-client, use [`applyMiddleware()`](http://redux.js.org/docs/api/applyMiddleware.html): 22 | 23 | ```js 24 | import { createStore, applyMiddleware } from 'redux' 25 | import swaggerClient from 'redux-swagger-client' 26 | import thunk from 'redux-thunk' 27 | 28 | const store = createStore( 29 | rootReducer, 30 | applyMiddleware([ 31 | thunk, 32 | swaggerClient({url:'http://petstore.swagger.io/v2/swagger.json'}) 33 | ]) 34 | ); 35 | ``` 36 | 37 | ## Usage 38 | ```js 39 | function fetchPet() { 40 | return { 41 | types: ["FETCH_PETS", "FETCH_PETS_SUCCESS", "FETCH_PETS_FAILED"], 42 | swagger: api => api.pet.findPetsByStatus({status: 'available'}) 43 | } 44 | } 45 | 46 | store.dispatch(fetchPets()) 47 | ``` 48 | 49 | Note, it's also possible to dispatch functions: 50 | 51 | ```js 52 | 53 | function fetchPets_request(json) { 54 | return { 55 | type: 'FETCH_PETS', 56 | receivedAt: Date.now() 57 | } 58 | } 59 | function fetchPets_success(json) { 60 | return { 61 | type: 'FETCH_PETS_SUCCESS', 62 | pets: json.result.body, 63 | receivedAt: Date.now() 64 | } 65 | } 66 | function fetchPets_failure(json) { 67 | return { 68 | type: 'FETCH_PETS_FAILED', 69 | pets: null, 70 | error, 71 | receivedAt: Date.now() 72 | } 73 | } 74 | 75 | function fetchPet() { 76 | return { 77 | actions: [fetchPets_request, fetchPets_success, fetchPets_failure], 78 | swagger: api => api.pet.findPetsByStatus({status: 'available'}) 79 | } 80 | } 81 | 82 | store.dispatch(fetchPets()) 83 | ``` 84 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-swagger-client", 3 | "version": "2.1.1", 4 | "description": "Swagger middleware for Redux.", 5 | "main": "lib/index.js", 6 | "jsnext:main": "es/index.js", 7 | "typings": "./index.d.ts", 8 | "files": ["lib", "es", "src", "dist", "index.d.ts"], 9 | "scripts": { 10 | "clean": "rimraf lib dist es", 11 | "build": "npm run build:commonjs && npm run build:umd && npm run build:umd:min && npm run build:es", 12 | "build:commonjs": "cross-env BABEL_ENV=commonjs babel src --out-dir lib", 13 | "build:es": "cross-env BABEL_ENV=es babel src --out-dir es", 14 | "build:umd": "cross-env BABEL_ENV=commonjs NODE_ENV=development webpack", 15 | "build:umd:min": "cross-env BABEL_ENV=commonjs NODE_ENV=production webpack" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/noh4ck/redux-swagger-client.git" 20 | }, 21 | "homepage": "https://github.com/noh4ck/redux-swagger-client", 22 | "keywords": ["redux", "swagger", "middleware", "redux-middleware", "flux"], 23 | "author": "Jonas Rudlang ", 24 | "license": "MIT", 25 | "dependencies": { 26 | "swagger-client": "^3.0.3" 27 | }, 28 | "devDependencies": { 29 | "babel-cli": "^6.24.1", 30 | "babel-core": "^6.6.5", 31 | "babel-eslint": "^5.0.0-beta4", 32 | "babel-loader": "^6.2.4", 33 | "babel-plugin-check-es2015-constants": "^6.6.5", 34 | "babel-plugin-transform-async-to-generator": "^6.24.1", 35 | "babel-plugin-transform-es2015-arrow-functions": "^6.5.2", 36 | "babel-plugin-transform-es2015-block-scoped-functions": "^6.6.5", 37 | "babel-plugin-transform-es2015-block-scoping": "^6.6.5", 38 | "babel-plugin-transform-es2015-classes": "^6.6.5", 39 | "babel-plugin-transform-es2015-computed-properties": "^6.6.5", 40 | "babel-plugin-transform-es2015-destructuring": "^6.6.5", 41 | "babel-plugin-transform-es2015-for-of": "^6.6.0", 42 | "babel-plugin-transform-es2015-function-name": "^6.5.0", 43 | "babel-plugin-transform-es2015-literals": "^6.5.0", 44 | "babel-plugin-transform-es2015-modules-commonjs": "^6.6.5", 45 | "babel-plugin-transform-es2015-object-super": "^6.6.5", 46 | "babel-plugin-transform-es2015-parameters": "^6.6.5", 47 | "babel-plugin-transform-es2015-shorthand-properties": "^6.5.0", 48 | "babel-plugin-transform-es2015-spread": "^6.6.5", 49 | "babel-plugin-transform-es2015-sticky-regex": "^6.5.0", 50 | "babel-plugin-transform-es2015-template-literals": "^6.6.5", 51 | "babel-plugin-transform-es2015-unicode-regex": "^6.5.0", 52 | "babel-plugin-transform-es3-member-expression-literals": "^6.5.0", 53 | "babel-plugin-transform-es3-property-literals": "^6.5.0", 54 | "babel-preset-es2015": "^6.24.1", 55 | "babel-preset-stage-2": "^6.17.0", 56 | "chai": "^3.2.0", 57 | "cross-env": "^1.0.7", 58 | "eslint": "^1.10.2", 59 | "eslint-config-airbnb": "1.0.2", 60 | "eslint-plugin-react": "^4.1.0", 61 | "mocha": "^2.2.5", 62 | "redux": "^3.4.0", 63 | "rimraf": "^2.5.2", 64 | "typescript": "^1.8.10", 65 | "typescript-definition-tester": "0.0.4", 66 | "uglify-js": "^3.0.15", 67 | "webpack": "^1.12.14" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.__esModule = true; 4 | 5 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 6 | 7 | exports['default'] = swaggerMiddleware; 8 | 9 | var _swaggerClient = require('swagger-client'); 10 | 11 | var _swaggerClient2 = _interopRequireDefault(_swaggerClient); 12 | 13 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 14 | 15 | function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } 16 | 17 | function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } 18 | 19 | function swaggerMiddleware(opts) { 20 | var _this = this; 21 | 22 | return function (_ref) { 23 | var dispatch = _ref.dispatch, 24 | getState = _ref.getState; 25 | return function (next) { 26 | return function (action) { 27 | if (typeof action === 'function') { 28 | return action(dispatch, getState); 29 | } 30 | 31 | if (!action.swagger) { 32 | return next(action); 33 | } 34 | 35 | return new _swaggerClient2['default'](_extends({}, opts, { 36 | requestInterceptor: function requestInterceptor(req) { 37 | return !!opts.requestInterceptor ? opts.requestInterceptor(req, action, dispatch, getState) : req; 38 | }, 39 | responseInterceptor: function responseInterceptor(resp) { 40 | return !!opts.responseInterceptor ? opts.responseInterceptor(resp, action, dispatch, getState) : resp; 41 | } 42 | })).then(function (result) { 43 | var swagger = action.swagger, 44 | types = action.types, 45 | actions = action.actions, 46 | rest = _objectWithoutProperties(action, ['swagger', 'types', 'actions']); 47 | 48 | var _ref2 = actions || types, 49 | REQUEST = _ref2[0], 50 | SUCCESS = _ref2[1], 51 | FAILURE = _ref2[2]; 52 | 53 | var callApi = function () { 54 | var _ref3 = _asyncToGenerator(regeneratorRuntime.mark(function _callee(apis, sw) { 55 | var error, resp; 56 | return regeneratorRuntime.wrap(function _callee$(_context) { 57 | while (1) { 58 | switch (_context.prev = _context.next) { 59 | case 0: 60 | if (!(typeof sw !== 'function')) { 61 | _context.next = 4; 62 | break; 63 | } 64 | 65 | error = new Error('Swagger api call is not a function'); 66 | 67 | opts.error && opts.error(error); 68 | throw error; 69 | 70 | case 4: 71 | if (typeof REQUEST === 'function') next(REQUEST(rest));else if (REQUEST) next(_extends({}, rest, { type: REQUEST })); 72 | _context.t0 = !!opts.preRequest; 73 | 74 | if (!_context.t0) { 75 | _context.next = 9; 76 | break; 77 | } 78 | 79 | _context.next = 9; 80 | return opts.preRequest(action); 81 | 82 | case 9: 83 | _context.prev = 9; 84 | _context.next = 12; 85 | return sw(apis, action); 86 | 87 | case 12: 88 | resp = _context.sent; 89 | 90 | if (!(typeof SUCCESS === 'function')) { 91 | _context.next = 17; 92 | break; 93 | } 94 | 95 | return _context.abrupt('return', next(SUCCESS(_extends({}, rest, { result: resp })))); 96 | 97 | case 17: 98 | if (!(SUCCESS !== undefined)) { 99 | _context.next = 19; 100 | break; 101 | } 102 | 103 | return _context.abrupt('return', next(_extends({}, rest, { result: resp, type: SUCCESS }))); 104 | 105 | case 19: 106 | _context.next = 29; 107 | break; 108 | 109 | case 21: 110 | _context.prev = 21; 111 | _context.t1 = _context['catch'](9); 112 | 113 | if (!(typeof FAILURE === 'function')) { 114 | _context.next = 27; 115 | break; 116 | } 117 | 118 | return _context.abrupt('return', next(FAILURE(_extends({}, rest, { error: _context.t1 })))); 119 | 120 | case 27: 121 | if (!(FAILURE !== undefined)) { 122 | _context.next = 29; 123 | break; 124 | } 125 | 126 | return _context.abrupt('return', next(_extends({}, rest, { error: _context.t1, type: FAILURE }))); 127 | 128 | case 29: 129 | case 'end': 130 | return _context.stop(); 131 | } 132 | } 133 | }, _callee, _this, [[9, 21]]); 134 | })); 135 | 136 | return function callApi(_x, _x2) { 137 | return _ref3.apply(this, arguments); 138 | }; 139 | }(); 140 | 141 | return callApi(result.apis, swagger); 142 | }, function (err) { 143 | return opts.error && opts.error(err); 144 | })['catch'](function (err) { 145 | return opts.error && opts.error(err); 146 | }); 147 | }; 148 | }; 149 | }; 150 | } --------------------------------------------------------------------------------