├── VERSION ├── .ruby-version ├── clients ├── go │ ├── .gitignore │ ├── Rakefile │ ├── template │ │ ├── go.mod.erb │ │ ├── authentication.go.erb │ │ ├── response.go.erb │ │ ├── authentication │ │ │ ├── basic.go.erb │ │ │ └── oauth2.go.erb │ │ ├── client.go.erb │ │ ├── resource.go.erb │ │ └── types.go.erb │ ├── bin │ │ └── haveapi-go-client │ ├── lib │ │ └── haveapi │ │ │ ├── go_client │ │ │ ├── version.rb │ │ │ ├── utils.rb │ │ │ ├── authentication │ │ │ │ ├── unsupported.rb │ │ │ │ ├── base.rb │ │ │ │ ├── basic.rb │ │ │ │ ├── oauth2.rb │ │ │ │ └── token.rb │ │ │ ├── parameters │ │ │ │ ├── global_meta_includes.rb │ │ │ │ ├── resource.rb │ │ │ │ ├── typed.rb │ │ │ │ ├── association.rb │ │ │ │ └── base.rb │ │ │ ├── authentication_methods.rb │ │ │ ├── api_version.rb │ │ │ ├── parameter.rb │ │ │ ├── cli.rb │ │ │ ├── erb_template.rb │ │ │ ├── input_output.rb │ │ │ └── metadata.rb │ │ │ └── go_client.rb │ ├── Gemfile │ ├── shell.nix │ ├── haveapi-go-client.gemspec │ └── README.md ├── ruby │ ├── Rakefile │ ├── .rubocop.yml │ ├── bin │ │ └── haveapi-cli │ ├── Gemfile │ ├── lib │ │ ├── haveapi │ │ │ ├── client │ │ │ │ ├── inflections.rb │ │ │ │ ├── version.rb │ │ │ │ ├── authentication │ │ │ │ │ ├── noauth.rb │ │ │ │ │ └── basic.rb │ │ │ │ ├── validators │ │ │ │ │ ├── custom.rb │ │ │ │ │ ├── acceptance.rb │ │ │ │ │ ├── exclusion.rb │ │ │ │ │ ├── presence.rb │ │ │ │ │ ├── inclusion.rb │ │ │ │ │ ├── format.rb │ │ │ │ │ ├── length.rb │ │ │ │ │ ├── confirmation.rb │ │ │ │ │ └── numericality.rb │ │ │ │ ├── parameters │ │ │ │ │ └── resource.rb │ │ │ │ ├── exceptions.rb │ │ │ │ ├── resource_instance_list.rb │ │ │ │ ├── validator.rb │ │ │ │ ├── action_state.rb │ │ │ │ └── params.rb │ │ │ ├── cli.rb │ │ │ ├── client.rb │ │ │ └── cli │ │ │ │ ├── utils.rb │ │ │ │ ├── commands │ │ │ │ └── action_state_wait.rb │ │ │ │ ├── authentication │ │ │ │ ├── basic.rb │ │ │ │ └── base.rb │ │ │ │ ├── command.rb │ │ │ │ └── example_formatter.rb │ │ └── restclient_ext │ │ │ └── resource.rb │ ├── shell.nix │ ├── haveapi-client.gemspec │ └── LICENSE.txt ├── elixir │ ├── test │ │ └── test_helper.exs │ ├── CHANGELOG │ ├── lib │ │ └── haveapi │ │ │ └── client │ │ │ ├── http.ex │ │ │ ├── authentication │ │ │ └── basic.ex │ │ │ ├── response.ex │ │ │ ├── protocol.ex │ │ │ └── authentication.ex │ ├── .gitignore │ ├── mix.exs │ └── config │ │ └── config.exs ├── js │ ├── src │ │ ├── haveapi │ │ │ ├── validators │ │ │ │ ├── custom.js │ │ │ │ ├── accept.js │ │ │ │ ├── confirm.js │ │ │ │ ├── exclude.js │ │ │ │ ├── include.js │ │ │ │ ├── present.js │ │ │ │ ├── format.js │ │ │ │ ├── length.js │ │ │ │ └── number.js │ │ │ ├── local_response.js │ │ │ ├── authentication.js │ │ │ ├── exceptions.js │ │ │ ├── authentication │ │ │ │ ├── basic.js │ │ │ │ └── base.js │ │ │ ├── resource.js │ │ │ ├── http.js │ │ │ └── response.js │ │ └── haveapi.js │ ├── shell.nix │ ├── bower.json │ ├── package.json │ ├── LICENSE.txt │ └── gulpfile.js └── php │ ├── src │ └── Client │ │ ├── Exception │ │ ├── Base.php │ │ ├── AuthenticationFailed.php │ │ ├── ObjectNotFound.php │ │ ├── ProtocolError.php │ │ ├── UnresolvedArguments.php │ │ └── ActionFailed.php │ │ └── Authentication │ │ ├── NoAuth.php │ │ ├── Basic.php │ │ └── Base.php │ ├── composer.json │ ├── bootstrap.php │ ├── CHANGELOG │ └── LICENSE.txt ├── servers ├── elixir │ ├── test │ │ ├── test_helper.exs │ │ └── haveapi │ │ │ ├── validator_test.exs │ │ │ ├── authentication_test.exs │ │ │ ├── protocol_test.exs │ │ │ └── meta_test.exs │ ├── lib │ │ ├── haveapi.ex │ │ └── haveapi │ │ │ ├── request.ex │ │ │ ├── action │ │ │ ├── update.ex │ │ │ ├── delete.ex │ │ │ ├── create.ex │ │ │ └── input.ex │ │ │ ├── response.ex │ │ │ ├── authentication.ex │ │ │ ├── context.ex │ │ │ ├── validator │ │ │ ├── acceptance.ex │ │ │ ├── exclusion.ex │ │ │ ├── inclusion.ex │ │ │ ├── format.ex │ │ │ ├── presence.ex │ │ │ └── confirmation.ex │ │ │ ├── authentication │ │ │ ├── chain.ex │ │ │ └── basic.ex │ │ │ ├── meta.ex │ │ │ ├── version.ex │ │ │ ├── parameter │ │ │ ├── output.ex │ │ │ └── input.ex │ │ │ └── resource.ex │ ├── CHANGELOG │ ├── .gitignore │ ├── mix.exs │ └── config │ │ └── config.exs └── ruby │ ├── .gitignore │ ├── lib │ ├── haveapi │ │ ├── version.rb │ │ ├── tasks │ │ │ ├── hooks.rb │ │ │ └── yard.rb │ │ ├── public │ │ │ ├── doc │ │ │ │ └── protocol.png │ │ │ ├── js │ │ │ │ ├── highlighter.js │ │ │ │ └── main.js │ │ │ └── css │ │ │ │ └── highlight.css │ │ ├── extensions │ │ │ ├── base.rb │ │ │ └── action_exceptions.rb │ │ ├── views │ │ │ ├── version_page │ │ │ │ ├── client_init.erb │ │ │ │ ├── client_auth.erb │ │ │ │ ├── client_example.erb │ │ │ │ └── auth_body.erb │ │ │ ├── doc_sidebars │ │ │ │ ├── json-schema.erb │ │ │ │ ├── create-client.erb │ │ │ │ └── protocol.erb │ │ │ ├── index.erb │ │ │ ├── version_sidebar │ │ │ │ ├── auth_nav.erb │ │ │ │ └── resource_nav.erb │ │ │ ├── doc_layout.erb │ │ │ └── version_sidebar.erb │ │ ├── exceptions.rb │ │ ├── spec │ │ │ ├── helpers.rb │ │ │ ├── api_response.rb │ │ │ └── mock_action.rb │ │ ├── authentication │ │ │ ├── token.rb │ │ │ ├── oauth2.rb │ │ │ ├── token │ │ │ │ ├── action_request.rb │ │ │ │ ├── action_result.rb │ │ │ │ └── action_config.rb │ │ │ ├── oauth2 │ │ │ │ └── revoke_endpoint.rb │ │ │ ├── basic │ │ │ │ └── provider.rb │ │ │ └── base.rb │ │ ├── output_formatters │ │ │ ├── json.rb │ │ │ └── base.rb │ │ ├── actions │ │ │ ├── paginable.rb │ │ │ └── default.rb │ │ ├── types.rb │ │ ├── example_list.rb │ │ ├── route.rb │ │ ├── validators │ │ │ ├── custom.rb │ │ │ ├── acceptance.rb │ │ │ ├── exclusion.rb │ │ │ ├── presence.rb │ │ │ ├── format.rb │ │ │ ├── confirmation.rb │ │ │ ├── inclusion.rb │ │ │ └── length.rb │ │ ├── model_adapters │ │ │ └── hash.rb │ │ ├── common.rb │ │ ├── metadata.rb │ │ └── output_formatter.rb │ └── haveapi.rb │ ├── spec │ ├── .rubocop.yml │ ├── validators │ │ ├── custom_spec.rb │ │ ├── acceptance_spec.rb │ │ ├── exclusion_spec.rb │ │ ├── inclusion_spec.rb │ │ ├── confirmation_spec.rb │ │ ├── presence_spec.rb │ │ ├── format_spec.rb │ │ └── length_spec.rb │ ├── spec_helper.rb │ ├── documentation_spec.rb │ ├── envelope_spec.rb │ ├── common_spec.rb │ └── resource_spec.rb │ ├── doc │ ├── index.md │ └── hooks.erb │ ├── Gemfile │ ├── shell.nix │ ├── Rakefile │ ├── haveapi.gemspec │ └── LICENSE.txt ├── templates └── ruby │ ├── activerecord │ ├── .gitignore │ ├── config.ru │ ├── models │ │ └── dummy.rb │ ├── db │ │ ├── migrate │ │ │ └── 0001_setup_database.rb │ │ └── schema.rb │ ├── Rakefile │ ├── spec │ │ ├── spec_helper.rb │ │ └── dummy_spec.rb │ ├── Gemfile │ ├── config │ │ └── database.yml │ └── lib │ │ └── api.rb │ ├── basic │ ├── config.ru │ ├── Gemfile │ ├── Rakefile │ ├── spec │ │ ├── spec_helper.rb │ │ └── dummy_spec.rb │ ├── lib │ │ ├── api.rb │ │ └── api │ │ │ └── resources │ │ │ └── dummy.rb │ └── README.md │ └── README.md ├── examples └── servers │ └── ruby │ ├── activerecord_auth │ ├── .gitignore │ ├── config.ru │ ├── lib │ │ ├── api │ │ │ └── authentication │ │ │ │ └── basic.rb │ │ └── api.rb │ ├── models │ │ ├── auth_token.rb │ │ └── user.rb │ ├── spec │ │ └── spec_helper.rb │ ├── shell.nix │ ├── config │ │ └── database.yml │ ├── Gemfile │ ├── Rakefile │ ├── db │ │ ├── migrate │ │ │ └── 0001_setup_database.rb │ │ └── schema.rb │ └── README.md │ └── README.md ├── doc └── protocol.png ├── Gemfile ├── .rubocop_todo.yml ├── .overcommit.yml ├── .php-cs-fixer.dist.php ├── .gitignore ├── utils └── copydoc.sh ├── .editorconfig └── shell.nix /VERSION: -------------------------------------------------------------------------------- 1 | 0.26.0 2 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.2.0 2 | -------------------------------------------------------------------------------- /clients/go/.gitignore: -------------------------------------------------------------------------------- 1 | .gems 2 | -------------------------------------------------------------------------------- /clients/go/Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | -------------------------------------------------------------------------------- /clients/go/template/go.mod.erb: -------------------------------------------------------------------------------- 1 | module <%= mod %> 2 | -------------------------------------------------------------------------------- /clients/ruby/Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | -------------------------------------------------------------------------------- /clients/elixir/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /servers/elixir/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /templates/ruby/activerecord/.gitignore: -------------------------------------------------------------------------------- 1 | db/*.sqlite3 2 | -------------------------------------------------------------------------------- /examples/servers/ruby/activerecord_auth/.gitignore: -------------------------------------------------------------------------------- 1 | db/*.sqlite3 2 | -------------------------------------------------------------------------------- /doc/protocol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpsfreecz/haveapi/HEAD/doc/protocol.png -------------------------------------------------------------------------------- /servers/ruby/.gitignore: -------------------------------------------------------------------------------- 1 | doc/protocol.* 2 | doc/create-client.md 3 | doc/json-schema.html 4 | -------------------------------------------------------------------------------- /templates/ruby/basic/config.ru: -------------------------------------------------------------------------------- 1 | require_relative 'lib/api' 2 | 3 | run API.default.app 4 | -------------------------------------------------------------------------------- /templates/ruby/activerecord/config.ru: -------------------------------------------------------------------------------- 1 | require_relative 'lib/api' 2 | 3 | run API.default.app 4 | -------------------------------------------------------------------------------- /examples/servers/ruby/activerecord_auth/config.ru: -------------------------------------------------------------------------------- 1 | require_relative 'lib/api' 2 | 3 | run API.default.app 4 | -------------------------------------------------------------------------------- /clients/js/src/haveapi/validators/custom.js: -------------------------------------------------------------------------------- 1 | Validator.validators.custom = function () { 2 | return true; 3 | }; 4 | -------------------------------------------------------------------------------- /clients/ruby/.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: ../../.rubocop.yml 2 | 3 | 4 | AllCops: 5 | TargetRubyVersion: 3.0 6 | -------------------------------------------------------------------------------- /clients/ruby/bin/haveapi-cli: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'haveapi/cli' 4 | 5 | HaveAPI::CLI::Cli.run 6 | -------------------------------------------------------------------------------- /clients/go/bin/haveapi-go-client: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'haveapi/go_client/cli' 3 | HaveAPI::GoClient::Cli.run 4 | -------------------------------------------------------------------------------- /servers/elixir/lib/haveapi.ex: -------------------------------------------------------------------------------- 1 | defmodule HaveAPI do 2 | @moduledoc """ 3 | Documentation for HaveAPI. 4 | """ 5 | end 6 | -------------------------------------------------------------------------------- /templates/ruby/activerecord/models/dummy.rb: -------------------------------------------------------------------------------- 1 | class Dummy < ActiveRecord::Base 2 | validate :name, presence: true 3 | end 4 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/version.rb: -------------------------------------------------------------------------------- 1 | module HaveAPI 2 | PROTOCOL_VERSION = '2.0'.freeze 3 | VERSION = '0.26.0'.freeze 4 | end 5 | -------------------------------------------------------------------------------- /servers/ruby/spec/.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: ../../../.rubocop.yml 2 | 3 | Lint/ConstantDefinitionInBlock: 4 | Enabled: false 5 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | group :development do 4 | gem 'overcommit' 5 | gem 'rubocop', '~> 1.75.0' 6 | end 7 | -------------------------------------------------------------------------------- /clients/elixir/CHANGELOG: -------------------------------------------------------------------------------- 1 | * See ../../CHANGELOG.md for newer versions 2 | 3 | * Mon Nov 27 2017 - version 0.11.0 4 | - Initial release 5 | -------------------------------------------------------------------------------- /clients/go/lib/haveapi/go_client/version.rb: -------------------------------------------------------------------------------- 1 | module HaveAPI 2 | module GoClient 3 | VERSION = '0.26.0'.freeze 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /servers/elixir/CHANGELOG: -------------------------------------------------------------------------------- 1 | * See ../../CHANGELOG.md for newer versions 2 | 3 | * Mon Nov 27 2017 - version 0.11.0 4 | - Initial release 5 | -------------------------------------------------------------------------------- /clients/js/src/haveapi/validators/accept.js: -------------------------------------------------------------------------------- 1 | Validator.validators.accept = function (opts, value) { 2 | return opts.value === value; 3 | }; 4 | -------------------------------------------------------------------------------- /servers/elixir/lib/haveapi/request.ex: -------------------------------------------------------------------------------- 1 | defmodule HaveAPI.Request do 2 | defstruct [:context, :conn, :params, :input, :meta, :user] 3 | end 4 | -------------------------------------------------------------------------------- /clients/ruby/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | group :development do 6 | gem 'bundler' 7 | gem 'rake' 8 | end 9 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/tasks/hooks.rb: -------------------------------------------------------------------------------- 1 | def document_hooks(file = 'doc/Hooks.md') 2 | render_doc_file('doc/hooks.erb', 'doc/Hooks.md') 3 | end 4 | -------------------------------------------------------------------------------- /clients/ruby/lib/haveapi/client/inflections.rb: -------------------------------------------------------------------------------- 1 | ActiveSupport::Inflector.inflections(:en) do |inflect| 2 | inflect.irregular 'vps', 'vpses' 3 | end 4 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/public/doc/protocol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpsfreecz/haveapi/HEAD/servers/ruby/lib/haveapi/public/doc/protocol.png -------------------------------------------------------------------------------- /clients/ruby/lib/haveapi/client/version.rb: -------------------------------------------------------------------------------- 1 | module HaveAPI 2 | module Client 3 | PROTOCOL_VERSION = '2.0'.freeze 4 | VERSION = '0.26.0'.freeze 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /servers/elixir/lib/haveapi/action/update.ex: -------------------------------------------------------------------------------- 1 | defmodule HaveAPI.Action.Update do 2 | use HaveAPI.Action 3 | 4 | method :put 5 | route "/:%{resource}_id" 6 | end 7 | -------------------------------------------------------------------------------- /servers/elixir/lib/haveapi/response.ex: -------------------------------------------------------------------------------- 1 | defmodule HaveAPI.Response do 2 | defstruct [:context, :conn, :status, :output, :meta, :message, :errors, :http_status] 3 | end 4 | -------------------------------------------------------------------------------- /templates/ruby/basic/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'haveapi', '~> 0.17.0' 4 | 5 | group :test do 6 | gem 'rack-test' 7 | gem 'rspec' 8 | end 9 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/extensions/base.rb: -------------------------------------------------------------------------------- 1 | module HaveAPI 2 | module Extensions 3 | class Base 4 | def self.enabled(server); end 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | Layout/LineLength: 2 | Enabled: false 3 | 4 | Lint/UnusedMethodArgument: 5 | Enabled: false 6 | 7 | Style/OptionalBooleanParameter: 8 | Enabled: false 9 | -------------------------------------------------------------------------------- /clients/go/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gemspec 3 | 4 | gem 'haveapi-client', path: '../ruby' 5 | 6 | group :development do 7 | gem 'bundler' 8 | gem 'rake' 9 | end 10 | -------------------------------------------------------------------------------- /clients/php/src/Client/Exception/Base.php: -------------------------------------------------------------------------------- 1 | <%= client.label %> 2 |
<%= client.init(host, base_url, api_version) %>
3 | -------------------------------------------------------------------------------- /.overcommit.yml: -------------------------------------------------------------------------------- 1 | PreCommit: 2 | RuboCop: 3 | enabled: true 4 | PhpCsFixer: 5 | enabled: true 6 | command: 'php-cs-fixer' 7 | CommitMsg: 8 | CapitalizedSubject: 9 | enabled: false 10 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/views/version_page/client_auth.erb: -------------------------------------------------------------------------------- 1 |

<%= client.label %>

2 |
<%= client.auth(host, base_url, api_version, method, desc) %>
3 | -------------------------------------------------------------------------------- /clients/js/src/haveapi/validators/confirm.js: -------------------------------------------------------------------------------- 1 | Validator.validators.confirm = function (opts, value, params) { 2 | var cond = value === params[ opts.parameter ]; 3 | 4 | return opts.equal ? cond : !cond; 5 | }; 6 | -------------------------------------------------------------------------------- /clients/php/src/Client/Exception/AuthenticationFailed.php: -------------------------------------------------------------------------------- 1 | {}; 3 | stdenv = pkgs.stdenv; 4 | 5 | in stdenv.mkDerivation rec { 6 | name = "haveapi-client-js"; 7 | 8 | buildInputs = with pkgs; [ 9 | nodejs 10 | ]; 11 | } 12 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/exceptions.rb: -------------------------------------------------------------------------------- 1 | require 'nesty' 2 | 3 | module HaveAPI 4 | class BuildError < StandardError 5 | include Nesty::NestedError 6 | end 7 | 8 | class AuthenticationError < StandardError; end 9 | end 10 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/spec/helpers.rb: -------------------------------------------------------------------------------- 1 | require 'rack/test' 2 | 3 | module HaveAPI 4 | module Spec; end 5 | end 6 | 7 | require_relative 'api_builder' 8 | require_relative 'api_response' 9 | require_relative 'spec_methods' 10 | -------------------------------------------------------------------------------- /templates/ruby/activerecord/db/migrate/0001_setup_database.rb: -------------------------------------------------------------------------------- 1 | class SetupDatabase < ActiveRecord::Migration 2 | def change 3 | create_table :dummies do |t| 4 | t.string :name, null: false, limit: 50 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /clients/js/src/haveapi/validators/exclude.js: -------------------------------------------------------------------------------- 1 | Validator.validators.exclude = function (opts, value) { 2 | if (opts.values instanceof Array) 3 | return opts.values.indexOf(value) === -1; 4 | 5 | return !opts.values.hasOwnProperty(value); 6 | }; 7 | -------------------------------------------------------------------------------- /clients/js/src/haveapi/validators/include.js: -------------------------------------------------------------------------------- 1 | Validator.validators.include = function (opts, value) { 2 | if (opts.values instanceof Array) 3 | return !(opts.values.indexOf(value) === -1); 4 | 5 | return opts.values.hasOwnProperty(value); 6 | }; 7 | -------------------------------------------------------------------------------- /servers/elixir/lib/haveapi/context.ex: -------------------------------------------------------------------------------- 1 | defmodule HaveAPI.Context do 2 | defstruct [ 3 | :api, 4 | :prefix, 5 | :version, 6 | :resource_path, 7 | :resource, 8 | :action, 9 | :conn, 10 | :user, 11 | ] 12 | end 13 | -------------------------------------------------------------------------------- /clients/ruby/lib/haveapi/cli.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/client' 2 | 3 | module HaveAPI 4 | module CLI 5 | module Commands; end 6 | end 7 | end 8 | 9 | require_rel 'cli/*.rb' 10 | require_rel 'cli/authentication/' 11 | require_rel 'cli/commands/' 12 | -------------------------------------------------------------------------------- /servers/ruby/spec/validators/custom_spec.rb: -------------------------------------------------------------------------------- 1 | describe HaveAPI::Validators::Custom do 2 | it 'always passes' do 3 | v = HaveAPI::Validators::Custom.new(:validate, 'some custom validation') 4 | expect(v.valid?('WHATEVER')).to be true 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /clients/ruby/lib/haveapi/client/validators/custom.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/client/validator' 2 | 3 | module HaveAPI::Client 4 | class Validators::Custom < Validator 5 | name :custom 6 | 7 | def valid? 8 | true 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /templates/ruby/basic/Rakefile: -------------------------------------------------------------------------------- 1 | require 'rspec/core' 2 | require 'rspec/core/rake_task' 3 | 4 | RSpec::Core::RakeTask.new(:spec) do |spec| 5 | spec.pattern = FileList['spec/*_spec.rb', 'spec/**/*_spec.rb'] 6 | spec.rspec_opts = '--require spec_helper' 7 | end 8 | -------------------------------------------------------------------------------- /servers/ruby/doc/index.md: -------------------------------------------------------------------------------- 1 | HaveAPI documentation 2 | ===================== 3 | 4 | - [README](doc/readme) 5 | - [Protocol definition](doc/protocol.md) 6 | - [JSON schema of the documentation protocol](doc/json-schema) 7 | - [How to create a client](doc/create-client.md) 8 | -------------------------------------------------------------------------------- /clients/go/template/authentication.go.erb: -------------------------------------------------------------------------------- 1 | package <%= package %> 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | // Authenticator is used to provide authentication for HTTP requests 8 | type Authenticator interface { 9 | Authenticate(request *http.Request) 10 | } 11 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/authentication/token.rb: -------------------------------------------------------------------------------- 1 | module HaveAPI::Authentication 2 | module Token 3 | # Configure the token provider 4 | # @param cfg [Config] 5 | def self.with_config(cfg) 6 | Provider.with_config(cfg) 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/authentication/oauth2.rb: -------------------------------------------------------------------------------- 1 | module HaveAPI::Authentication 2 | module OAuth2 3 | # Configure the oauth2 provider 4 | # @param cfg [Config] 5 | def self.with_config(cfg) 6 | Provider.with_config(cfg) 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /clients/ruby/lib/haveapi/client/validators/acceptance.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/client/validator' 2 | 3 | module HaveAPI::Client 4 | class Validators::Acceptance < Validator 5 | name :accept 6 | 7 | def valid? 8 | value == opts[:value] 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /.php-cs-fixer.dist.php: -------------------------------------------------------------------------------- 1 | in(__DIR__) 5 | ; 6 | 7 | return (new PhpCsFixer\Config()) 8 | ->setRules([ 9 | '@PER-CS' => true, 10 | '@PHP82Migration' => true, 11 | ]) 12 | ->setFinder($finder) 13 | ; 14 | -------------------------------------------------------------------------------- /clients/js/src/haveapi/validators/present.js: -------------------------------------------------------------------------------- 1 | Validator.validators.present = function (opts, value) { 2 | if (value === undefined) 3 | return false; 4 | 5 | if (!opts.empty && typeof value === 'string' && !value.trim().length) 6 | return false; 7 | 8 | return true; 9 | }; 10 | -------------------------------------------------------------------------------- /clients/php/src/Client/Exception/UnresolvedArguments.php: -------------------------------------------------------------------------------- 1 | = 6.0' 13 | gem 'sinatra-activerecord' 14 | end 15 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/tasks/yard.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/tasks/hooks' 2 | 3 | def render_doc_file(src, dst) 4 | src = File.join(File.dirname(__FILE__), '..', '..', '..', src) 5 | 6 | proc do 7 | File.write( 8 | dst, 9 | ERB.new(File.read(src)).result(binding) 10 | ) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /clients/js/src/haveapi/validators/format.js: -------------------------------------------------------------------------------- 1 | Validator.validators.format = function (opts, value) { 2 | if (typeof value != 'string') 3 | return false; 4 | 5 | var rx = new RegExp(opts.rx); 6 | 7 | if (opts.match) 8 | return value.match(rx) ? true : false; 9 | 10 | return value.match(rx) ? false : true; 11 | }; 12 | -------------------------------------------------------------------------------- /examples/servers/ruby/activerecord_auth/lib/api/authentication/basic.rb: -------------------------------------------------------------------------------- 1 | module API::Authentication 2 | class Basic < HaveAPI::Authentication::Basic::Provider 3 | protected 4 | 5 | def find_user(request, username, password) 6 | ::User.authenticate(request, username, password) 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .php-cs-fixer.cache 6 | .yardoc 7 | .gems 8 | Gemfile.lock 9 | InstalledFiles 10 | _yardoc 11 | coverage 12 | lib/bundler/man 13 | pkg 14 | rdoc 15 | spec/reports 16 | test/tmp 17 | test/version_tmp 18 | tmp 19 | *.bundle 20 | *.so 21 | *.o 22 | *.a 23 | mkmf.log 24 | dist 25 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/views/doc_sidebars/json-schema.erb: -------------------------------------------------------------------------------- 1 |

Contents

2 | 8 | -------------------------------------------------------------------------------- /clients/php/src/Client/Authentication/NoAuth.php: -------------------------------------------------------------------------------- 1 | ... 4 | 5 | DOC="./doc" 6 | 7 | for srv in $@ ; do 8 | dst="$srv/doc" 9 | [ ! -d "$dst" ] && mkdir "$dst" 10 | erb "$DOC"/json-schema.erb > "$dst"/json-schema.html 11 | cp "$DOC"/*.md "$DOC"/*.png "$dst/" 12 | done 13 | -------------------------------------------------------------------------------- /templates/ruby/activerecord/Rakefile: -------------------------------------------------------------------------------- 1 | require 'active_record' 2 | require 'sinatra/activerecord/rake' 3 | require 'haveapi' 4 | require 'rspec/core' 5 | require 'rspec/core/rake_task' 6 | 7 | RSpec::Core::RakeTask.new(:spec) do |spec| 8 | spec.pattern = FileList['spec/*_spec.rb', 'spec/**/*_spec.rb'] 9 | spec.rspec_opts = '--require spec_helper' 10 | end 11 | -------------------------------------------------------------------------------- /templates/ruby/basic/spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi' 2 | require 'haveapi/spec/helpers' 3 | require_relative '../lib/api' 4 | 5 | ENV['RACK_ENV'] = 'test' 6 | 7 | # Configure specs 8 | RSpec.configure do |config| 9 | config.order = 'random' 10 | 11 | config.extend HaveAPI::Spec::ApiBuilder 12 | config.include HaveAPI::Spec::SpecMethods 13 | end 14 | -------------------------------------------------------------------------------- /templates/ruby/activerecord/spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi' 2 | require 'haveapi/spec/helpers' 3 | require_relative '../lib/api' 4 | 5 | ENV['RACK_ENV'] = 'test' 6 | 7 | # Configure specs 8 | RSpec.configure do |config| 9 | config.order = 'random' 10 | 11 | config.extend HaveAPI::Spec::ApiBuilder 12 | config.include HaveAPI::Spec::SpecMethods 13 | end 14 | -------------------------------------------------------------------------------- /clients/go/template/response.go.erb: -------------------------------------------------------------------------------- 1 | package <%= package %> 2 | 3 | // Envelope represents a response from the API server 4 | type Envelope struct { 5 | // Determines action success 6 | Status bool 7 | 8 | // Error message is Status is false 9 | Message string 10 | 11 | // Errors for individual parameters if Status is false 12 | Errors map[string][]string 13 | } 14 | -------------------------------------------------------------------------------- /examples/servers/ruby/activerecord_auth/models/auth_token.rb: -------------------------------------------------------------------------------- 1 | class AuthToken < ActiveRecord::Base 2 | belongs_to :user 3 | 4 | enum lifetime: %i[fixed renewable_manual renewable_auto permanent] 5 | 6 | validates :token, presence: true 7 | validates :token, length: { is: 100 } 8 | 9 | def renew 10 | self.valid_to = Time.now + interval 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /clients/ruby/lib/haveapi/client/validators/presence.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/client/validator' 2 | 3 | module HaveAPI::Client 4 | class Validators::Presence < Validator 5 | name :present 6 | 7 | def valid? 8 | return false if value.nil? 9 | return !value.strip.empty? if !opts[:empty] && value.is_a?(::String) 10 | 11 | true 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /examples/servers/ruby/activerecord_auth/spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi' 2 | require 'haveapi/spec/helpers' 3 | require_relative '../lib/api' 4 | 5 | ENV['RACK_ENV'] = 'test' 6 | 7 | # Configure specs 8 | RSpec.configure do |config| 9 | config.order = 'random' 10 | 11 | config.extend HaveAPI::Spec::ApiBuilder 12 | config.include HaveAPI::Spec::SpecMethods 13 | end 14 | -------------------------------------------------------------------------------- /examples/servers/ruby/README.md: -------------------------------------------------------------------------------- 1 | HaveAPI Server Examples 2 | ======================= 3 | 4 | This repository contains example server projects, demonstrating some HaveAPI 5 | functionality. See `README.md` in each example's directory to learn more about 6 | it. 7 | 8 | ## ActiveRecord Auth 9 | Project with an example implementation of HaveAPI's authentication backends, 10 | `basic` and `token`. 11 | -------------------------------------------------------------------------------- /clients/go/lib/haveapi/go_client/authentication/unsupported.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/go_client/authentication/base' 2 | 3 | module HaveAPI::GoClient 4 | class Authentication::Unsupported < Authentication::Base 5 | def initialize(api_version, name, desc) 6 | super 7 | warn "Ignoring unsupported authentication method #{name}" 8 | end 9 | 10 | def generate(gen); end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /clients/js/src/haveapi/local_response.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class LocalResponse 3 | * @memberof HaveAPI.Client 4 | * @augments HaveAPI.Client.Response 5 | */ 6 | function LocalResponse (action, status, message, errors) { 7 | this.action = action; 8 | this.envelope = { 9 | status: status, 10 | message: message, 11 | errors: errors, 12 | }; 13 | }; 14 | 15 | LocalResponse.prototype = new Response(); 16 | -------------------------------------------------------------------------------- /clients/ruby/lib/haveapi/client/validators/inclusion.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/client/validator' 2 | 3 | module HaveAPI::Client 4 | class Validators::Inclusion < Validator 5 | name :include 6 | 7 | def valid? 8 | if opts[:values].is_a?(::Hash) 9 | opts[:values].keys.include?(value) 10 | 11 | else 12 | opts[:values].include?(value) 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/views/index.erb: -------------------------------------------------------------------------------- 1 |

API description

2 |

Available versions:

3 | 12 |

Documentation

13 | -------------------------------------------------------------------------------- /clients/ruby/lib/haveapi/client.rb: -------------------------------------------------------------------------------- 1 | require 'require_all' 2 | require 'date' 3 | 4 | module HaveAPI 5 | module Client 6 | # Shortcut to HaveAPI::Client::Client.new 7 | def self.new(*args) 8 | HaveAPI::Client::Client.new(*args) 9 | end 10 | end 11 | end 12 | 13 | require_rel 'client/*.rb' 14 | require_rel 'client/authentication/' 15 | require_rel 'client/parameters/' 16 | require_rel 'client/validators/' 17 | -------------------------------------------------------------------------------- /clients/go/lib/haveapi/go_client/authentication/base.rb: -------------------------------------------------------------------------------- 1 | module HaveAPI::GoClient 2 | class Authentication::Base 3 | # @param name [Symbol] 4 | def self.register(name) 5 | AuthenticationMethods.register(name, self) 6 | end 7 | 8 | def initialize(api_version, name, desc); end 9 | 10 | # @param dst [String] 11 | def generate(dst) 12 | raise NotImplementedError 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /clients/ruby/lib/haveapi/client/validators/format.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/client/validator' 2 | 3 | module HaveAPI::Client 4 | class Validators::Format < Validator 5 | name :format 6 | 7 | def valid? 8 | rx = Regexp.new(opts[:rx]) 9 | 10 | if opts[:match] 11 | rx.match(value) ? true : false 12 | 13 | else 14 | rx.match(value) ? false : true 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /clients/ruby/shell.nix: -------------------------------------------------------------------------------- 1 | let 2 | pkgs = import {}; 3 | stdenv = pkgs.stdenv; 4 | 5 | in stdenv.mkDerivation rec { 6 | name = "haveapi-client"; 7 | 8 | buildInputs = with pkgs;[ 9 | ruby 10 | git 11 | openssl 12 | ]; 13 | 14 | shellHook = '' 15 | export GEM_HOME=$(pwd)/../../.gems 16 | export PATH="$GEM_HOME/bin:$PATH" 17 | gem install bundler 18 | bundle install 19 | ''; 20 | } 21 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/output_formatters/json.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/output_formatters/base' 2 | 3 | module HaveAPI::OutputFormatters 4 | class Json < BaseFormatter 5 | handle 'application/json' 6 | 7 | def format(response) 8 | if ENV['RACK_ENV'] == 'development' 9 | JSON.pretty_generate(response) 10 | 11 | else 12 | JSON.generate(response) 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /servers/ruby/shell.nix: -------------------------------------------------------------------------------- 1 | let 2 | pkgs = import {}; 3 | stdenv = pkgs.stdenv; 4 | 5 | in stdenv.mkDerivation rec { 6 | name = "haveapi"; 7 | 8 | buildInputs = with pkgs;[ 9 | ruby_3_2 10 | git 11 | openssl 12 | ]; 13 | 14 | shellHook = '' 15 | export GEM_HOME=$(pwd)/../../.gems 16 | export PATH="$GEM_HOME/bin:$PATH" 17 | gem install bundler 18 | bundle install 19 | ''; 20 | } 21 | -------------------------------------------------------------------------------- /templates/ruby/activerecord/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'activerecord', '~> 7.0.8' 4 | gem 'haveapi', '~> 0.17.0' 5 | gem 'sinatra-activerecord', '~> 2.0.26' 6 | 7 | # Enable needed database adapters 8 | # gem 'pg' 9 | # gem 'mysql2' 10 | gem 'sqlite3' 11 | 12 | # Used to recursively require resources and models 13 | gem 'require_all' 14 | 15 | group :test do 16 | gem 'rack-test' 17 | gem 'rspec' 18 | end 19 | -------------------------------------------------------------------------------- /clients/ruby/lib/restclient_ext/resource.rb: -------------------------------------------------------------------------------- 1 | module RestClient 2 | class Resource 3 | def get_options(additional_headers = {}, &block) 4 | headers = (options[:headers] || {}).merge(additional_headers) 5 | Request.execute(options.merge( 6 | method: :options, 7 | url: url, 8 | headers: headers 9 | ), &block || @block) 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /servers/ruby/spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'require_all' 2 | require_relative '../lib/haveapi' 3 | require_rel '../lib/haveapi/spec/*.rb' 4 | 5 | module HaveAPI 6 | module Spec 7 | module API 8 | end 9 | end 10 | end 11 | 12 | require_rel 'api' 13 | 14 | # Configure specs 15 | RSpec.configure do |config| 16 | config.order = 'random' 17 | 18 | config.extend HaveAPI::Spec::ApiBuilder 19 | config.include HaveAPI::Spec::SpecMethods 20 | end 21 | -------------------------------------------------------------------------------- /clients/php/src/Client/Authentication/Basic.php: -------------------------------------------------------------------------------- 1 | authenticateWith($this->opts['user'], $this->opts['password']); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/actions/paginable.rb: -------------------------------------------------------------------------------- 1 | module HaveAPI::Actions 2 | module Paginable 3 | def self.included(action) 4 | action.input do 5 | integer :from_id, label: 'From ID', desc: 'List objects with greater/lesser ID', 6 | number: { min: 0 } 7 | integer :limit, label: 'Limit', desc: 'Number of objects to retrieve', 8 | number: { min: 0 } 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /servers/elixir/lib/haveapi/validator/acceptance.ex: -------------------------------------------------------------------------------- 1 | defmodule HaveAPI.Validator.Acceptance do 2 | use HaveAPI.Validator 3 | 4 | def name, do: :accept 5 | 6 | def init(opts) do 7 | %{ 8 | value: Keyword.fetch!(opts, :value), 9 | message: opts[:message] || "has to be '#{inspect(opts[:value])}'" 10 | } 11 | end 12 | 13 | def validate(%{value: v}, v, _params), do: :ok 14 | def validate(opts, _v, _params), do: {:error, [opts.message]} 15 | end 16 | -------------------------------------------------------------------------------- /examples/servers/ruby/activerecord_auth/shell.nix: -------------------------------------------------------------------------------- 1 | let 2 | pkgs = import {}; 3 | stdenv = pkgs.stdenv; 4 | 5 | in stdenv.mkDerivation rec { 6 | name = "haveapi-example-servers-ruby"; 7 | 8 | buildInputs = with pkgs; [ 9 | git 10 | ruby 11 | sqlite 12 | ]; 13 | 14 | shellHook = '' 15 | export GEM_HOME=$(pwd)/../../../../.gems 16 | export PATH="$GEM_HOME/bin:$PATH" 17 | gem install bundler 18 | bundle install 19 | ''; 20 | } 21 | -------------------------------------------------------------------------------- /clients/go/lib/haveapi/go_client/authentication/basic.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/go_client/authentication/base' 2 | 3 | module HaveAPI::GoClient 4 | class Authentication::Basic < Authentication::Base 5 | register :basic 6 | 7 | def generate(gen) 8 | ErbTemplate.render_to_if_changed( 9 | 'authentication/basic.go', 10 | { 11 | package: gen.package 12 | }, 13 | File.join(gen.dst, 'auth_basic.go') 14 | ) 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /clients/elixir/lib/haveapi/client/http.ex: -------------------------------------------------------------------------------- 1 | defmodule HaveAPI.Client.Http do 2 | def start, do: HTTPoison.start 3 | 4 | def options(url, opts) do 5 | HTTPoison.options(url, opts[:headers] || [], params: opts[:params] || []) 6 | end 7 | 8 | def request(method, url, body \\ "", headers \\ [], options \\ []) do 9 | HTTPoison.request( 10 | method |> String.downcase |> String.to_atom, 11 | url, 12 | body, 13 | headers, 14 | options 15 | ) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /clients/go/shell.nix: -------------------------------------------------------------------------------- 1 | let 2 | pkgs = import {}; 3 | stdenv = pkgs.stdenv; 4 | 5 | in stdenv.mkDerivation rec { 6 | name = "haveapi-go-client"; 7 | 8 | buildInputs = with pkgs;[ 9 | git 10 | go 11 | gotools 12 | openssl 13 | ruby_3_2 14 | ]; 15 | 16 | shellHook = '' 17 | export GEM_HOME=$(pwd)/.gems 18 | export PATH="$GEM_HOME/.gems/bin:$PATH" 19 | gem install bundler 20 | bundler install 21 | export RUBYOPT=-rbundler/setup 22 | ''; 23 | } 24 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/types.rb: -------------------------------------------------------------------------------- 1 | # Just to represent boolean type in self-description 2 | module Boolean 3 | def self.to_b(str) 4 | return true if str === true 5 | return false if str === false 6 | 7 | if str.respond_to?(:=~) 8 | return true if str =~ /^(true|t|yes|y|1)$/i 9 | return false if str =~ /^(false|f|no|n|0)$/i 10 | end 11 | 12 | false 13 | end 14 | end 15 | 16 | module Datetime 17 | end 18 | 19 | module Custom 20 | end 21 | 22 | class Text < String 23 | end 24 | -------------------------------------------------------------------------------- /templates/ruby/activerecord/config/database.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: sqlite3 3 | encoding: utf8 4 | # host: localhost 5 | # username: api 6 | # password: secret 7 | database: db/development.sqlite3 8 | 9 | production: 10 | adapter: sqlite3 11 | encoding: utf8 12 | # host: localhost 13 | # username: api 14 | # password: secret 15 | database: db/production.sqlite3 16 | 17 | test: 18 | adapter: sqlite3 19 | database: db/test.sqlite3 20 | # pool: 5 21 | # timeout: 5000 22 | -------------------------------------------------------------------------------- /clients/ruby/lib/haveapi/client/validators/length.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/client/validator' 2 | 3 | module HaveAPI::Client 4 | class Validators::Length < Validator 5 | name :length 6 | 7 | def valid? 8 | len = value.length 9 | 10 | return len == opts[:equals] if opts[:equals] 11 | return len >= opts[:min] if opts[:min] && !opts[:max] 12 | return len <= opts[:max] if !opts[:min] && opts[:max] 13 | 14 | len >= opts[:min] && len <= opts[:max] 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /servers/ruby/spec/documentation_spec.rb: -------------------------------------------------------------------------------- 1 | describe 'Documentation' do 2 | it 'responds to OPTIONS /' do 3 | end 4 | 5 | it 'responds to OPTIONS /?describe=versions' do 6 | end 7 | 8 | it 'responds to OPTIONS /?describe=default' do 9 | end 10 | 11 | it 'responds to OPTIONS /' do 12 | end 13 | 14 | it 'responds to OPTIONS /?method=' do 15 | end 16 | 17 | it 'has online doc' do 18 | end 19 | 20 | it 'has online doc for every version' do 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /examples/servers/ruby/activerecord_auth/config/database.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: sqlite3 3 | encoding: utf8 4 | # host: localhost 5 | # username: api 6 | # password: secret 7 | database: db/development.sqlite3 8 | 9 | production: 10 | adapter: sqlite3 11 | encoding: utf8 12 | # host: localhost 13 | # username: api 14 | # password: secret 15 | database: db/production.sqlite3 16 | 17 | test: 18 | adapter: sqlite3 19 | database: db/test.sqlite3 20 | # pool: 5 21 | # timeout: 5000 22 | -------------------------------------------------------------------------------- /clients/ruby/lib/haveapi/client/authentication/basic.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/client/authentication/base' 2 | 3 | module HaveAPI::Client::Authentication 4 | class Basic < Base 5 | register :basic 6 | 7 | def resource 8 | RestClient::Resource.new( 9 | @communicator.url, 10 | user: @opts[:user], 11 | password: @opts[:password], 12 | verify_ssl: @communicator.verify_ssl 13 | ) 14 | end 15 | 16 | def user = @opts.[](:user) 17 | def password = @opts.[](:password) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /templates/ruby/basic/spec/dummy_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Dummy' do 4 | api API::Resources 5 | use_version '1.0' 6 | 7 | it 'returns a list of dummies' do 8 | call_api :get, '/v1.0/dummies' 9 | expect(api_response).to be_ok 10 | expect(api_response.response[:dummies].length).to eq(3) 11 | end 12 | 13 | it 'returns a dummy' do 14 | call_api :get, '/v1.0/dummies/1' 15 | expect(api_response).to be_ok 16 | expect(api_response.response[:dummy][:name]).to eq('Second') 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /clients/go/lib/haveapi/go_client/parameters/global_meta_includes.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/go_client/parameters/base' 2 | 3 | module HaveAPI::GoClient 4 | class Parameters::GlobalMetaIncludes < Parameters::Base 5 | handle do |role, direction, name, desc| 6 | role == :global_meta \ 7 | && direction == :input \ 8 | && name == 'includes' \ 9 | && desc[:type] == 'Custom' 10 | end 11 | 12 | protected 13 | 14 | def do_resolve 15 | @go_in_type = @go_out_type = 'string' 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /clients/js/src/haveapi/validators/length.js: -------------------------------------------------------------------------------- 1 | Validator.validators.length = function (opts, value) { 2 | if (typeof value != 'string') 3 | return false; 4 | 5 | var len = value.length; 6 | 7 | if (typeof opts.equals === 'number') 8 | return len === opts.equals; 9 | 10 | if (typeof opts.min === 'number' && !(typeof opts.max === 'number')) 11 | return len >= opts.min; 12 | 13 | if (!(typeof opts.min === 'number') && typeof opts.max === 'number') 14 | return len <= opts.max; 15 | 16 | return len >= opts.min && len <= opts.max; 17 | }; 18 | -------------------------------------------------------------------------------- /clients/ruby/lib/haveapi/client/validators/confirmation.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/client/validator' 2 | 3 | module HaveAPI::Client 4 | class Validators::Confirmation < Validator 5 | name :confirm 6 | 7 | def valid? 8 | other = opts[:parameter].to_sym 9 | 10 | if opts[:equal] 11 | return false if params[other].nil? 12 | 13 | value == params[other].value 14 | 15 | else 16 | other = params[other] ? params[other].value : nil 17 | value != other 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /templates/ruby/activerecord/spec/dummy_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Dummy' do 4 | api API::Resources 5 | use_version '1.0' 6 | 7 | it 'returns a list of dummies' do 8 | call_api :get, '/v1.0/dummies' 9 | expect(api_response).to be_ok 10 | expect(api_response.response[:dummies].length).to eq(3) 11 | end 12 | 13 | it 'returns a dummy' do 14 | call_api :get, '/v1.0/dummies/1' 15 | expect(api_response).to be_ok 16 | expect(api_response.response[:dummy][:name]).to eq('Second') 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /clients/elixir/lib/haveapi/client/authentication/basic.ex: -------------------------------------------------------------------------------- 1 | defmodule HaveAPI.Client.Authentication.Basic do 2 | alias HaveAPI.Client 3 | 4 | @behaviour Client.Authentication 5 | 6 | def setup(_conn, opts) do 7 | {:ok, %{username: opts[:username], password: opts[:password]}} 8 | end 9 | 10 | def authenticate(req, opts) do 11 | Client.Request.add_header( 12 | req, 13 | "Authorization", 14 | "basic " <> Base.encode64("#{opts.username}:#{opts.password}") 15 | ) 16 | end 17 | 18 | def logout(_conn, _opts), do: :ok 19 | end 20 | -------------------------------------------------------------------------------- /examples/servers/ruby/activerecord_auth/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'activerecord', '~> 7.0.8' 4 | gem 'haveapi', '~> 0.26.0', path: '../../../../servers/ruby' 5 | gem 'rake' 6 | gem 'sinatra-activerecord', '~> 2.0.26' 7 | 8 | # Enable needed database adapters 9 | # gem 'pg' 10 | # gem 'mysql2' 11 | gem 'sqlite3', '~> 1.6.6' 12 | 13 | # Used to recursively require resources and models 14 | gem 'require_all' 15 | 16 | # Password hashing algorithm 17 | gem 'bcrypt' 18 | 19 | group :test do 20 | gem 'rack-test' 21 | gem 'rspec' 22 | end 23 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/views/version_page/client_example.erb: -------------------------------------------------------------------------------- 1 |
<%= client.label %>
2 | <% sample = client.new(host, base_url, api_version, r_name, resource, a_name, action) %> 3 | <% if sample.respond_to?(:example) %> 4 |
<%= sample.example(example) %>
5 | 6 | <% else %> 7 |
Request
8 |
<%= sample.request(example) %>
9 |
Response
10 |
<%= sample.response(example) %>
11 | <% end %> 12 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/example_list.rb: -------------------------------------------------------------------------------- 1 | module HaveAPI 2 | class ExampleList 3 | def initialize 4 | @examples = [] 5 | end 6 | 7 | # @param example [Example] 8 | def <<(example) 9 | @examples << example 10 | end 11 | 12 | def describe(context) 13 | ret = [] 14 | 15 | @examples.each do |e| 16 | ret << e.describe(context) if e.authorized?(context) 17 | end 18 | 19 | ret 20 | end 21 | 22 | def each(&) 23 | @examples.each(&) 24 | end 25 | 26 | include Enumerable 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/route.rb: -------------------------------------------------------------------------------- 1 | module HaveAPI 2 | class Route 3 | attr_reader :path, :sinatra_path, :action, :resource_path 4 | 5 | def initialize(path, action, resource_path) 6 | @path = path 7 | @sinatra_path = path.gsub(/:([a-zA-Z\-_]+)/, '{\1}') 8 | @action = action 9 | @resource_path = resource_path 10 | end 11 | 12 | def http_method 13 | @action.http_method 14 | end 15 | 16 | def description 17 | @action.desc 18 | end 19 | 20 | def params 21 | @action.params 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /servers/elixir/lib/haveapi/authentication/chain.ex: -------------------------------------------------------------------------------- 1 | defmodule HaveAPI.Authentication.Chain do 2 | def authenticate(conn, [chain: chain]) do 3 | user = Enum.reduce_while( 4 | chain, 5 | nil, 6 | fn auth, acc -> 7 | case auth.authenticate(conn) do 8 | :halt -> 9 | {:halt, acc} 10 | 11 | ^acc -> 12 | {:cont, acc} 13 | 14 | user -> 15 | {:halt, user} 16 | end 17 | end 18 | ) 19 | 20 | %{conn | private: Map.put(conn.private, :haveapi_user, user)} 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /clients/elixir/lib/haveapi/client/response.ex: -------------------------------------------------------------------------------- 1 | defmodule HaveAPI.Client.Response do 2 | defstruct [:conn, :status, :response, :message, :errors] 3 | 4 | def new(conn, data) when is_map(data) do 5 | %__MODULE__{ 6 | conn: conn, 7 | status: data.body["status"], 8 | response: data.body["response"], 9 | message: data.body["message"], 10 | errors: data.body["errors"] 11 | } 12 | end 13 | 14 | def ok?(resp), do: resp.status === true 15 | 16 | def params(resp) do 17 | resp.response[resp.conn.action_desc["output"]["namespace"]] 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /clients/go/template/authentication/basic.go.erb: -------------------------------------------------------------------------------- 1 | package <%= package %> 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | type BasicAuth struct { 8 | Username string 9 | Password string 10 | } 11 | 12 | func (auth *BasicAuth) Authenticate(request *http.Request) { 13 | request.SetBasicAuth(auth.Username, auth.Password) 14 | } 15 | 16 | // SetBasicAuthentication enables HTTP basic auth on the client 17 | func (client *Client) SetBasicAuthentication(username string, password string) { 18 | client.Authentication = &BasicAuth{ 19 | Username: username, 20 | Password: password, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/authentication/token/action_request.rb: -------------------------------------------------------------------------------- 1 | module HaveAPI::Authentication 2 | module Token 3 | class ActionRequest 4 | # @return [Sinatra::Request] 5 | attr_reader :request 6 | 7 | # @return [Hash] 8 | attr_reader :input 9 | 10 | # @return [Object, nil] 11 | attr_reader :user 12 | 13 | # @return [String, nil] 14 | attr_reader :token 15 | 16 | def initialize(opts = {}) 17 | opts.each do |k, v| 18 | instance_variable_set(:"@#{k}", v) 19 | end 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | trim_trailing_whitespace = true 8 | 9 | [*.{rb,erb}] 10 | indent_size = 2 11 | indent_style = space 12 | 13 | [*.{go.erb}] 14 | indent_size = 4 15 | indent_style = tab 16 | 17 | [*.php] 18 | indent_size = 4 19 | indent_style = space 20 | 21 | [*.plantuml] 22 | indent_size = 2 23 | indent_style = tab 24 | 25 | [*.js] 26 | indent_size = 2 27 | indent_style = tab 28 | 29 | [*.ex] 30 | indent_size = 2 31 | indent_style = space 32 | 33 | [Makefile] 34 | trim_trailing_whitespace = false 35 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/validators/custom.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/validator' 2 | 3 | module HaveAPI 4 | # Custom validator. It has only a short form, taking the description 5 | # of the validator. This validator passes every value. It is up to the 6 | # developer to implement the validation in HaveAPI::Action.exec. 7 | class Validators::Custom < Validator 8 | name :custom 9 | takes :validate 10 | 11 | def setup 12 | @desc = take 13 | end 14 | 15 | def describe 16 | @desc 17 | end 18 | 19 | def valid?(v) 20 | true 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /clients/elixir/.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | -------------------------------------------------------------------------------- /clients/go/lib/haveapi/go_client.rb: -------------------------------------------------------------------------------- 1 | require 'require_all' 2 | 3 | module HaveAPI 4 | module GoClient 5 | # @param name [String] template within the `../../template/` directory, 6 | # without `.erb` suffix 7 | # @return [String] absolute path to the template 8 | def self.tpl(name) 9 | File.join( 10 | File.dirname(__FILE__), 11 | '..', '..', 12 | 'template', 13 | "#{name}.erb" 14 | ) 15 | end 16 | end 17 | end 18 | 19 | require_rel 'go_client/*.rb' 20 | require_rel 'go_client/authentication' 21 | require_rel 'go_client/parameters' 22 | -------------------------------------------------------------------------------- /templates/ruby/README.md: -------------------------------------------------------------------------------- 1 | HaveAPI Server Project Templates 2 | ================================ 3 | This repository contains empty project templates for APIs built using the 4 | HaveAPI server framework in Ruby. There are several projects, with different 5 | model backends and configurations. You can use these templates when you want to 6 | start a new project. 7 | 8 | See `README.md` in project directories to learn more about them. 9 | 10 | ## Basic 11 | This is an empty project with no model backend. 12 | 13 | ## ActiveRecord 14 | This project uses ActiveRecord, a well-known ORM from Ruby on Rails, as a model 15 | backend. 16 | -------------------------------------------------------------------------------- /clients/ruby/lib/haveapi/client/parameters/resource.rb: -------------------------------------------------------------------------------- 1 | module HaveAPI::Client 2 | class Parameters::Resource 3 | attr_reader :errors 4 | 5 | def initialize(params, desc, value) 6 | @errors = [] 7 | @value = coerce(value) 8 | end 9 | 10 | def valid? 11 | @errors.empty? 12 | end 13 | 14 | def to_api 15 | @value 16 | end 17 | 18 | protected 19 | 20 | def coerce(v) 21 | if !v.is_a?(::Integer) && /\A\d+\z/ !~ v 22 | @errors << 'not a valid resource id' 23 | nil 24 | 25 | else 26 | v.to_i 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /clients/ruby/lib/haveapi/client/exceptions.rb: -------------------------------------------------------------------------------- 1 | module HaveAPI::Client 2 | class ProtocolError < StandardError; end 3 | 4 | class ActionFailed < StandardError 5 | attr_reader :response 6 | 7 | def initialize(response) 8 | super("#{response.action.name} failed: #{response.message}") 9 | 10 | @response = response 11 | end 12 | end 13 | 14 | class ValidationError < ActionFailed 15 | attr_reader :errors 16 | 17 | def initialize(action, errors) 18 | super("#{action.name} failed: input parameters not valid") 19 | 20 | @action = action 21 | @errors = errors 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /clients/go/template/client.go.erb: -------------------------------------------------------------------------------- 1 | package <%= package %> 2 | 3 | // Client represents a connection to an API server 4 | type Client struct { 5 | // API URL 6 | Url string 7 | 8 | // Options for authentication method 9 | Authentication Authenticator 10 | 11 | <% api.resources.each do |r| -%> 12 | // Resource <%= r.full_dot_name %> 13 | <%= r.go_name %> *<%= r.go_type %> 14 | <% end -%> 15 | } 16 | 17 | // Create a new client for API at url 18 | func New(url string) *Client { 19 | c := &Client{Url: url} 20 | 21 | <% api.resources.each do |r| -%> 22 | c.<%= r.go_name %> = New<%= r.go_type %>(c) 23 | <% end -%> 24 | 25 | return c 26 | } 27 | -------------------------------------------------------------------------------- /clients/php/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "haveapi/client", 3 | "description": "A client for HaveAPI based APIs", 4 | "version": "0.26.0", 5 | "homepage": "https://github.com/vpsfreecz/haveapi-client-php", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Jakub Skokan", 10 | "email": "jakub.skokan@vpsfree.cz" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=8.0.0", 15 | "league/oauth2-client": "~2.7.0", 16 | "vpsfreecz/httpful": "~0.4.0" 17 | }, 18 | "autoload": { 19 | "psr-4": { 20 | "HaveAPI\\": "src/" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /clients/ruby/lib/haveapi/cli/utils.rb: -------------------------------------------------------------------------------- 1 | require 'highline/import' 2 | 3 | module HaveAPI::CLI 4 | module Utils 5 | def param_option(name, p) 6 | ret = '--' 7 | name = name.to_s.dasherize 8 | 9 | ret += if p[:type] == 'Boolean' 10 | "[no-]#{name}" 11 | 12 | else 13 | "#{name} [#{name.underscore.upcase}]" 14 | end 15 | 16 | ret 17 | end 18 | 19 | def read_param(name, p) 20 | prompt = "#{p[:label] || name}: " 21 | 22 | ask(prompt) do |q| 23 | q.default = nil 24 | q.echo = !p[:protected] 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /templates/ruby/basic/lib/api.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi' 2 | 3 | module API 4 | # API resources are stored in this module 5 | module Resources; end 6 | 7 | # When a resource has no version set, this one will be used 8 | HaveAPI.implicit_version = '1.0' 9 | 10 | # Return API server with a default configuration 11 | # @return [HaveAPI::Server] 12 | def self.default 13 | api = HaveAPI::Server.new(Resources) 14 | 15 | # Include all detected API versions 16 | api.use_version(:all) 17 | 18 | # Register routes for all actions 19 | api.mount('/') 20 | 21 | api 22 | end 23 | end 24 | 25 | require_relative 'api/resources/dummy' 26 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/output_formatters/base.rb: -------------------------------------------------------------------------------- 1 | module HaveAPI::OutputFormatters 2 | class BaseFormatter 3 | class << self 4 | attr_reader :types 5 | 6 | def handle(*args) 7 | @types ||= [] 8 | @types += args 9 | 10 | HaveAPI::OutputFormatter.register(Kernel.const_get(to_s)) unless @registered 11 | @registered = true 12 | end 13 | 14 | def handle?(type) 15 | @types.detect do |t| 16 | File.fnmatch(type, t) 17 | end 18 | end 19 | end 20 | 21 | def content_type 22 | self.class.types.first 23 | end 24 | 25 | def format(response); end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/views/version_sidebar/auth_nav.erb: -------------------------------------------------------------------------------- 1 |
    2 | <% methods.each do |method, info| %> 3 |
  • 4 | <%= method.to_s.humanize %> 5 | <% if info[:resources] %> 6 |
      7 | <% sort_hash(info[:resources]).each do |resource, info| %> 8 | <%= 9 | erb :"version_sidebar/resource_nav", locals: { 10 | resource: resource.to_s, 11 | info: info, 12 | prefix: "auth-#{method}", 13 | } 14 | %> 15 | <% end %> 16 |
    17 | <% end %> 18 |
  • 19 | <% end %> 20 |
21 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/views/version_sidebar/resource_nav.erb: -------------------------------------------------------------------------------- 1 |
  • 2 | ">.<%= resource.humanize %> 3 | 4 |
      5 | <% sort_hash(info[:actions]).each do |action, info| %> 6 |
    • 7 | ">#<%= action.capitalize %> 8 |
    • 9 | <% end %> 10 | 11 | <% sort_hash(info[:resources]).each do |r, i| %> 12 | <%= 13 | erb :"version_sidebar/resource_nav", locals: { 14 | resource: r, 15 | info: i, 16 | prefix: "#{prefix}-#{resource}", 17 | } 18 | %> 19 | <% end %> 20 |
    21 |
  • 22 | -------------------------------------------------------------------------------- /servers/elixir/.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | doc/protocol.* 23 | doc/json-schema.html 24 | doc/create-client.md 25 | -------------------------------------------------------------------------------- /clients/js/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "haveapi-client", 3 | "description": "Client for HaveAPI based APIs", 4 | "main": "dist/haveapi-client.js", 5 | "authors": [ 6 | {"name": "Jakub Skokan", "email": "jakub.skokan@vpsfree.cz"} 7 | ], 8 | "license": "MIT", 9 | "moduleType": [ 10 | "globals", 11 | "node" 12 | ], 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/vpsfreecz/haveapi-client-js" 16 | }, 17 | "ignore": [ 18 | "**/.*", 19 | "node_modules", 20 | "bower_components", 21 | "test", 22 | "tests" 23 | ], 24 | "keywords": [ 25 | "rest", 26 | "api", 27 | "client", 28 | "http" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /clients/go/lib/haveapi/go_client/authentication_methods.rb: -------------------------------------------------------------------------------- 1 | module HaveAPI::GoClient 2 | module Authentication; end 3 | 4 | module AuthenticationMethods 5 | # @param name [Symbol] 6 | # @param klass [Class] 7 | def self.register(name, klass) 8 | @methods ||= {} 9 | @methods[name] = klass 10 | end 11 | 12 | # @param name [String] 13 | def self.get(name) 14 | @methods[name.to_sym] 15 | end 16 | 17 | # @param api_version [ApiVersion] 18 | # @param name [String] 19 | def self.new(api_version, name, *) 20 | klass = get(name) || Authentication::Unsupported 21 | klass.new(api_version, name, *) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /clients/go/lib/haveapi/go_client/parameters/resource.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/go_client/parameters/base' 2 | 3 | module HaveAPI::GoClient 4 | class Parameters::Resource < Parameters::Base 5 | handle do |_role, _direction, _name, desc| 6 | desc[:type] == 'Resource' 7 | end 8 | 9 | # Pointer to the associated resource 10 | # @return [Parameters::Association] 11 | attr_reader :association 12 | 13 | def nillable? 14 | true 15 | end 16 | 17 | protected 18 | 19 | def do_resolve 20 | @association = Parameters::Association.new(self, desc) 21 | @go_in_type = 'int64' 22 | @go_out_type = association.go_type 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /clients/js/src/haveapi/authentication.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @namespace Authentication 3 | * @memberof HaveAPI.Client 4 | */ 5 | Authentication = { 6 | /** 7 | * @member {Array} providers An array of registered authentication providers. 8 | * @memberof HaveAPI.Client.Authentication 9 | */ 10 | providers: {}, 11 | 12 | /** 13 | * Register authentication providers using this function. 14 | * @func registerProvider 15 | * @memberof HaveAPI.Client.Authentication 16 | * @param {string} name must be the same name as in announced by the API 17 | * @param {Object} provider class 18 | */ 19 | registerProvider: function(name, obj) { 20 | Authentication.providers[name] = obj; 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /servers/elixir/lib/haveapi/validator/exclusion.ex: -------------------------------------------------------------------------------- 1 | defmodule HaveAPI.Validator.Exclusion do 2 | use HaveAPI.Validator 3 | 4 | def name, do: :exclude 5 | 6 | def init(opts) do 7 | %{ 8 | values: Keyword.fetch!(opts, :values), 9 | message: opts[:message] || "%{value} cannot be used" 10 | } 11 | end 12 | 13 | def validate(%{values: values} = opts, v, _params) when is_list(values) do 14 | return(opts, v in values) 15 | end 16 | 17 | def validate(%{values: values} = opts, v, _params) when is_map(values) do 18 | return(opts, Map.has_key?(values, v)) 19 | end 20 | 21 | def return(opts, true), do: {:error, [opts.message]} 22 | def return(_opts, false), do: :ok 23 | end 24 | -------------------------------------------------------------------------------- /servers/elixir/lib/haveapi/validator/inclusion.ex: -------------------------------------------------------------------------------- 1 | defmodule HaveAPI.Validator.Inclusion do 2 | use HaveAPI.Validator 3 | 4 | def name, do: :include 5 | 6 | def init(opts) do 7 | %{ 8 | values: Keyword.fetch!(opts, :values), 9 | message: opts[:message] || "%{value} cannot be used" 10 | } 11 | end 12 | 13 | def validate(%{values: values} = opts, v, _params) when is_list(values) do 14 | return(opts, v in values) 15 | end 16 | 17 | def validate(%{values: values} = opts, v, _params) when is_map(values) do 18 | return(opts, Map.has_key?(values, v)) 19 | end 20 | 21 | def return(_opts, true), do: :ok 22 | def return(opts, false), do: {:error, [opts.message]} 23 | end 24 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/model_adapters/hash.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/model_adapter' 2 | 3 | module HaveAPI::ModelAdapters 4 | # Simple hash adapter. Model is just a hash of parameters 5 | # and their values. 6 | class Hash < ::HaveAPI::ModelAdapter 7 | register 8 | 9 | def self.handle?(layout, klass) 10 | klass.is_a?(::Hash) 11 | end 12 | 13 | class Input < ::HaveAPI::ModelAdapter::Input 14 | def self.clean(model, raw) 15 | raw 16 | end 17 | end 18 | 19 | class Output < ::HaveAPI::ModelAdapter::Output 20 | def has_param?(name) 21 | @object.has_key?(name) 22 | end 23 | 24 | def [](name) 25 | @object[name] 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /clients/go/lib/haveapi/go_client/api_version.rb: -------------------------------------------------------------------------------- 1 | module HaveAPI::GoClient 2 | class ApiVersion 3 | # @return [Array] 4 | attr_reader :auth_methods 5 | 6 | # @return [String] 7 | attr_reader :metadata_namespace 8 | 9 | # @return [Array] 10 | attr_reader :resources 11 | 12 | def initialize(desc) 13 | @resources = desc[:resources].map do |k, v| 14 | Resource.new(self, k, v) 15 | end.sort! 16 | 17 | @resources.each(&:resolve_associations) 18 | 19 | @auth_methods = desc[:authentication].map do |k, v| 20 | AuthenticationMethods.new(self, k, v) 21 | end 22 | 23 | @metadata_namespace = desc[:meta][:namespace] 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /clients/js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "haveapi-client", 3 | "version": "0.26.0", 4 | "description": "Client for HaveAPI based APIs", 5 | "license": "MIT", 6 | "author": { 7 | "name": "Jakub Skokan", 8 | "email": "jakub.skokan@vpsfree.cz" 9 | }, 10 | "files": [ 11 | "dist" 12 | ], 13 | "main": "dist/haveapi-client.js", 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/vpsfreecz/haveapi-client-js" 17 | }, 18 | "dependencies": { 19 | "xmlhttprequest": "latest" 20 | }, 21 | "devDependencies": { 22 | "gulp": "latest", 23 | "gulp-concat": "latest", 24 | "gulp-umd": "latest", 25 | "gulp-jsdoc3": "3.0.0" 26 | }, 27 | "keywords": [ 28 | "rest", 29 | "api", 30 | "client", 31 | "http" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /servers/ruby/spec/validators/acceptance_spec.rb: -------------------------------------------------------------------------------- 1 | describe HaveAPI::Validators::Acceptance do 2 | shared_examples(:all) do 3 | it 'accepts correct value' do 4 | expect(@v.valid?('foo')).to be true 5 | end 6 | 7 | it 'rejects incorrect value' do 8 | expect(@v.valid?('bar')).to be false 9 | end 10 | end 11 | 12 | context 'short form' do 13 | before(:each) do 14 | @v = HaveAPI::Validators::Acceptance.new(:accept, 'foo') 15 | end 16 | 17 | include_examples :all 18 | end 19 | 20 | context 'full form' do 21 | before(:each) do 22 | @v = HaveAPI::Validators::Acceptance.new(:accept, { 23 | value: 'foo' 24 | }) 25 | end 26 | 27 | include_examples :all 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/public/js/main.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | $(document).ready(function () { 4 | if (window.Worker) { 5 | var worker = new Worker('/js/highlighter.js'); 6 | var codes = $('pre code').toArray(); 7 | var currentElement; 8 | 9 | var highlightNext = function () { 10 | if (!codes.length) 11 | return; 12 | 13 | currentElement = codes.shift(); 14 | worker.postMessage({ 15 | language: currentElement.classList.item(0), 16 | code: currentElement.textContent 17 | }); 18 | }; 19 | 20 | worker.onmessage = function (e) { 21 | currentElement.innerHTML = e.data; 22 | highlightNext(); 23 | }; 24 | 25 | highlightNext(); 26 | 27 | } else { 28 | hljs.initHighlighting(); 29 | } 30 | }); 31 | 32 | })(); 33 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | let 2 | pkgs = import {}; 3 | stdenv = pkgs.stdenv; 4 | 5 | in stdenv.mkDerivation rec { 6 | name = "haveapi"; 7 | 8 | buildInputs = with pkgs; [ 9 | git 10 | gnumake 11 | nodejs 12 | php83Packages.php-cs-fixer 13 | ruby_3_2 14 | ]; 15 | 16 | shellHook = '' 17 | export GEM_HOME=$(pwd)/.gems 18 | export PATH="$GEM_HOME/bin:$PATH" 19 | gem install --no-document bundler 20 | 21 | # Purity disabled because of prism gem, which has a native extension. 22 | # The extension has its header files in .gems, which gets stripped but 23 | # cc wrapper in Nix. Without NIX_ENFORCE_PURITY=0, we get prism.h not found 24 | # error. 25 | NIX_ENFORCE_PURITY=0 bundle install 26 | ''; 27 | } 28 | -------------------------------------------------------------------------------- /clients/js/src/haveapi/validators/number.js: -------------------------------------------------------------------------------- 1 | Validator.validators.number = function (opts, value) { 2 | var v = (typeof value === 'string') ? parseInt(value) : value; 3 | 4 | if (typeof opts.min === 'number' && v < opts.min) 5 | return false; 6 | 7 | if (typeof opts.max === 'number' && v > opts.max) 8 | return false; 9 | 10 | if (typeof opts.step === 'number') { 11 | if ( (v - (typeof opts.min === 'number' ? opts.min : 0)) % opts.step > 0 ) 12 | return false; 13 | } 14 | 15 | if (typeof opts.mod === 'number' && !(v % opts.mod === 0)) 16 | return false; 17 | 18 | if (typeof opts.odd === 'number' && v % 2 === 0) 19 | return false; 20 | 21 | if (typeof opts.even === 'number' && v % 2 > 0) 22 | return false; 23 | 24 | return true; 25 | }; 26 | -------------------------------------------------------------------------------- /templates/ruby/activerecord/lib/api.rb: -------------------------------------------------------------------------------- 1 | # ActiveRecord must be required before HaveAPI 2 | require 'active_record' 3 | require 'haveapi' 4 | 5 | module API 6 | # API resources are stored in this module 7 | module Resources; end 8 | 9 | # When a resource has no version set, this one will be used 10 | HaveAPI.implicit_version = '1.0' 11 | 12 | # Return API server with a default configuration 13 | # @return [HaveAPI::Server] 14 | def self.default 15 | api = HaveAPI::Server.new(Resources) 16 | 17 | # Include all detected API versions 18 | api.use_version(:all) 19 | 20 | # Register routes for all actions 21 | api.mount('/') 22 | 23 | api 24 | end 25 | end 26 | 27 | require_rel '../models' 28 | require_rel 'api/resources' 29 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/extensions/action_exceptions.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/extensions/base' 2 | 3 | module HaveAPI::Extensions 4 | class ActionExceptions < Base 5 | class << self 6 | def enabled(server) 7 | HaveAPI::Action.connect_hook(:exec_exception) do |ret, _context, e| 8 | break(ret) unless @exceptions 9 | 10 | @exceptions.each do |handler| 11 | if e.is_a?(handler[:klass]) 12 | ret = handler[:block].call(ret, e) 13 | break 14 | end 15 | end 16 | 17 | ret 18 | end 19 | end 20 | 21 | def rescue(klass, &block) 22 | @exceptions ||= [] 23 | @exceptions << { klass:, block: } 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /servers/elixir/lib/haveapi/validator/format.ex: -------------------------------------------------------------------------------- 1 | defmodule HaveAPI.Validator.Format do 2 | use HaveAPI.Validator 3 | 4 | def init(opts) do 5 | %{ 6 | rx: Keyword.fetch!(opts, :rx), 7 | match: Keyword.get(opts, :match, true), 8 | message: opts[:message] || "%{value} cannot be used" 9 | } 10 | end 11 | 12 | def describe(opts) do 13 | %{opts | rx: Regex.source(opts.rx)} 14 | end 15 | 16 | def validate(%{match: true} = opts, v, _params) do 17 | return(opts, Regex.match?(opts[:rx], v)) 18 | end 19 | 20 | def validate(%{match: false} = opts, v, _params) do 21 | return(opts, not Regex.match?(opts[:rx], v)) 22 | end 23 | 24 | def return(_opts, true), do: :ok 25 | def return(opts, false), do: {:error, [opts.message]} 26 | end 27 | -------------------------------------------------------------------------------- /examples/servers/ruby/activerecord_auth/models/user.rb: -------------------------------------------------------------------------------- 1 | require 'bcrypt' 2 | 3 | class User < ActiveRecord::Base 4 | has_many :auth_tokens 5 | 6 | validates :username, :password, presence: true 7 | validates :username, length: { maximum: 50 } 8 | 9 | # Attempt to authenticate user 10 | # @return [User] if authenticated 11 | # @return [nil] if not 12 | def self.authenticate(request, username, password) 13 | user = find_by(username:) 14 | return unless user 15 | 16 | begin 17 | return user if ::BCrypt::Password.new(user.password) == password 18 | rescue BCrypt::Errors::InvalidHash 19 | # returning false 20 | end 21 | 22 | false 23 | end 24 | 25 | def self.hash_password(pwd) 26 | ::BCrypt::Password.create(pwd).to_s 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /clients/go/lib/haveapi/go_client/parameter.rb: -------------------------------------------------------------------------------- 1 | module HaveAPI::GoClient 2 | module Parameters; end 3 | 4 | module Parameter 5 | # @param klass [Class] 6 | # @param block [Proc] 7 | def self.register(klass, block) 8 | @handlers ||= [] 9 | @handlers << [klass, block] 10 | end 11 | 12 | # @param role [Symbol] 13 | # @param direction [Symbol] 14 | # @param io [InputOutput] 15 | # @param name [String] 16 | # @param desc [Hash] 17 | # @return [Parameters::Base, nil] 18 | def self.new(role, direction, io, name, desc) 19 | klass, = 20 | @handlers.select do |_klass, block| 21 | block.call(role, direction, name, desc) 22 | end.first 23 | 24 | klass ? klass.new(io, name, desc) : nil 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/spec/api_response.rb: -------------------------------------------------------------------------------- 1 | module HaveAPI::Spec 2 | # This class wraps raw reply from the API and provides a more friendly 3 | # interface. 4 | class ApiResponse 5 | def initialize(body) 6 | @data = JSON.parse(body, symbolize_names: true) 7 | end 8 | 9 | def envelope 10 | @data 11 | end 12 | 13 | def status 14 | @data[:status] 15 | end 16 | 17 | def ok? 18 | @data[:status] 19 | end 20 | 21 | def failed? 22 | !ok? 23 | end 24 | 25 | def response 26 | @data[:response] 27 | end 28 | 29 | def message 30 | @data[:message] 31 | end 32 | 33 | def errors 34 | @data[:errors] 35 | end 36 | 37 | def [](k) 38 | @data[:response][k] 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /clients/ruby/lib/haveapi/cli/commands/action_state_wait.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/cli/command' 2 | 3 | module HaveAPI::CLI::Commands 4 | class ActionStateWait < HaveAPI::CLI::Command 5 | cmd :action_state, :wait 6 | args '' 7 | desc 'Block until the action is finished' 8 | 9 | def exec(args) 10 | if args.empty? 11 | warn 'Provide argument STATE ID' 12 | exit(false) 13 | end 14 | 15 | @api.set_opts(block: false) 16 | 17 | state = HaveAPI::CLI::ActionState.new( 18 | @global_opts, 19 | @api, 20 | args.first.to_i 21 | ) 22 | ret = state.wait_for_completion(timeout: @global_opts[:timeout]) 23 | 24 | return unless ret.nil? 25 | 26 | warn 'Timeout' 27 | exit(false) 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /clients/ruby/lib/haveapi/client/validators/numericality.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/client/validator' 2 | 3 | module HaveAPI::Client 4 | class Validators::Numericality < Validator 5 | name :number 6 | 7 | def valid? 8 | if value.is_a?(::String) 9 | return false if /\A\d+\z/ !~ value 10 | 11 | v = value.to_i 12 | 13 | else 14 | v = value 15 | end 16 | 17 | ret = true 18 | ret = false if opts[:min] && v < opts[:min] 19 | ret = false if opts[:max] && v > opts[:max] 20 | ret = false if opts[:step] && (v - (opts[:min] || 0)) % opts[:step] != 0 21 | ret = false if opts[:mod] && v % opts[:mod] != 0 22 | ret = false if opts[:odd] && v.even? 23 | ret = false if opts[:even] && v % 2 > 0 24 | ret 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /examples/servers/ruby/activerecord_auth/Rakefile: -------------------------------------------------------------------------------- 1 | require 'active_record' 2 | require 'sinatra/activerecord/rake' 3 | require 'haveapi' 4 | require 'rspec/core' 5 | require 'rspec/core/rake_task' 6 | require_relative 'lib/api' 7 | 8 | RSpec::Core::RakeTask.new(:spec) do |spec| 9 | spec.pattern = FileList['spec/*_spec.rb', 'spec/**/*_spec.rb'] 10 | spec.rspec_opts = '--require spec_helper' 11 | end 12 | 13 | desc 'Create admin user' 14 | task :create_admin do 15 | vals = {} 16 | 17 | %i[username password].each do |v| 18 | $stdout.write("#{v.capitalize}: ") 19 | $stdout.flush 20 | 21 | vals[v] = $stdin.readline.strip 22 | end 23 | 24 | ::User.create!( 25 | username: vals[:username], 26 | password: ::User.hash_password(vals[:password]), 27 | is_admin: true 28 | ) 29 | end 30 | -------------------------------------------------------------------------------- /clients/go/lib/haveapi/go_client/parameters/typed.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/go_client/parameters/base' 2 | 3 | module HaveAPI::GoClient 4 | class Parameters::Typed < Parameters::Base 5 | handle do |_role, _direction, _name, desc| 6 | !%w[Custom Resource].include?(desc[:type]) 7 | end 8 | 9 | protected 10 | 11 | def do_resolve 12 | @go_in_type = get_go_type(desc[:type]) 13 | @go_out_type = get_go_type(desc[:type]) 14 | end 15 | 16 | def get_go_type(v) 17 | case v 18 | when 'String', 'Text', 'Datetime' 19 | 'string' 20 | when 'Integer' 21 | 'int64' 22 | when 'Float' 23 | 'float64' 24 | when 'Boolean' 25 | 'bool' 26 | else 27 | raise "unsupported data type '#{v}'" 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /clients/ruby/lib/haveapi/cli/authentication/basic.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/cli/authentication/base' 2 | 3 | module HaveAPI::CLI::Authentication 4 | class Basic < Base 5 | register :basic 6 | 7 | def options(opts) 8 | opts.on('--user USER', 'User name') do |u| 9 | @user = u 10 | end 11 | 12 | opts.on('--password PASSWORD', 'Password') do |p| 13 | @password = p 14 | end 15 | end 16 | 17 | def validate 18 | @user ||= ask('User name: ') { |q| q.default = nil }.to_s 19 | 20 | @password ||= ask('Password: ') do |q| 21 | q.default = nil 22 | q.echo = false 23 | end.to_s 24 | 25 | nil 26 | end 27 | 28 | def authenticate 29 | @communicator.authenticate(:basic, { user: @user, password: @password }) 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /clients/php/bootstrap.php: -------------------------------------------------------------------------------- 1 | Contents 2 | 21 | -------------------------------------------------------------------------------- /servers/elixir/lib/haveapi/validator/presence.ex: -------------------------------------------------------------------------------- 1 | defmodule HaveAPI.Validator.Presence do 2 | use HaveAPI.Validator 3 | 4 | def name, do: :present 5 | 6 | def init(opts) do 7 | empty = Keyword.get(opts, :empty, false) 8 | 9 | %{ 10 | empty: empty, 11 | message: Keyword.get( 12 | opts, 13 | :message, 14 | (if empty, do: "must be present", else: "must be present and non-empty") 15 | ) 16 | } 17 | end 18 | 19 | def validate(%{empty: true}, _v, _params), do: :ok 20 | def validate(opts, v, _params) when is_binary(v), do: not_empty(opts, String.trim(v)) 21 | def validate(opts, v, _params), do: not_empty(opts, v) 22 | 23 | defp not_empty(opts, nil), do: {:error, [opts.message]} 24 | defp not_empty(opts, ""), do: {:error, [opts.message]} 25 | defp not_empty(_opts, _v), do: :ok 26 | end 27 | -------------------------------------------------------------------------------- /servers/ruby/Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'rspec/core' 3 | require 'rspec/core/rake_task' 4 | require 'active_support/core_ext/string/inflections' 5 | require 'haveapi' 6 | require 'haveapi/tasks/yard' 7 | 8 | RSpec::Core::RakeTask.new(:spec) do |spec| 9 | spec.pattern = FileList['spec/**/*_spec.rb'] 10 | spec.rspec_opts = '--require spec_helper' 11 | end 12 | 13 | begin 14 | require 'yard' 15 | 16 | YARD::Rake::YardocTask.new do |t| 17 | t.files = ['lib/**/*.rb'] 18 | t.options = [ 19 | '--protected', 20 | '--output-dir=html_doc', 21 | '--files=doc/*.md', 22 | '--files=doc/*.html' 23 | ] 24 | t.before = proc do 25 | document_hooks.call 26 | render_doc_file('doc/json-schema.erb', 'doc/JSON-Schema.html').call 27 | end 28 | end 29 | rescue LoadError 30 | # ignore 31 | end 32 | -------------------------------------------------------------------------------- /clients/go/haveapi-go-client.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path('lib', __dir__) 2 | $:.unshift(lib) unless $:.include?(lib) 3 | require 'haveapi/go_client/version' 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = 'haveapi-go-client' 7 | spec.version = HaveAPI::GoClient::VERSION 8 | spec.authors = ['Jakub Skokan'] 9 | spec.email = ['jakub.skokan@vpsfree.cz'] 10 | spec.summary = 11 | spec.description = 'Go client generator' 12 | spec.homepage = '' 13 | spec.license = 'MIT' 14 | 15 | spec.required_ruby_version = ">= #{File.read('../../.ruby-version').strip}" 16 | 17 | spec.files = `git ls-files -z`.split("\x0") 18 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 19 | spec.require_paths = ['lib'] 20 | 21 | spec.add_dependency 'haveapi-client', '~> 0.26.0' 22 | end 23 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/views/doc_layout.erb: -------------------------------------------------------------------------------- 1 | <% yield %> 2 |
    3 |
    4 | <%= @content %> 5 |
    6 | 7 |
    8 | 11 |
    12 |
    13 | 14 | 28 | -------------------------------------------------------------------------------- /servers/ruby/spec/envelope_spec.rb: -------------------------------------------------------------------------------- 1 | describe 'Envelope' do 2 | context 'documentation' do 3 | empty_api 4 | 5 | it 'returns correct envelope' do 6 | call_api(:options, '/') 7 | expect(api_response.envelope.keys).to contain_exactly( 8 | *%i[version status response message errors] 9 | ) 10 | end 11 | 12 | it 'succeeds' do 13 | call_api(:options, '/') 14 | expect(api_response).to be_ok 15 | end 16 | end 17 | 18 | context 'data' do 19 | empty_api 20 | 21 | it 'returns correct envelope' do 22 | call_api(:get, '/unknown_resource') 23 | expect(api_response.envelope.keys).to contain_exactly( 24 | *%i[status response message errors] 25 | ) 26 | end 27 | 28 | it 'fails' do 29 | call_api(:get, '/unknown_resource') 30 | expect(api_response).to_not be_ok 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /clients/go/lib/haveapi/go_client/authentication/oauth2.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/go_client/authentication/base' 2 | 3 | module HaveAPI::GoClient 4 | class Authentication::OAuth2 < Authentication::Base 5 | register :oauth2 6 | 7 | # HTTP header the token is sent in 8 | # @return [String] 9 | attr_reader :http_header 10 | 11 | # Token revocation URL 12 | # @return [String] 13 | attr_reader :revoke_url 14 | 15 | def initialize(api_version, name, desc) 16 | super 17 | @http_header = desc[:http_header] 18 | @revoke_url = desc[:revoke_url] 19 | end 20 | 21 | def generate(gen) 22 | ErbTemplate.render_to_if_changed( 23 | 'authentication/oauth2.go', 24 | { 25 | package: gen.package, 26 | auth: self 27 | }, 28 | File.join(gen.dst, 'auth_oauth2.go') 29 | ) 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /servers/elixir/lib/haveapi/meta.ex: -------------------------------------------------------------------------------- 1 | defmodule HaveAPI.Meta do 2 | defmacro __using__(_opts) do 3 | quote do 4 | @haveapi_parent_input_layout nil 5 | @haveapi_parent_input [] 6 | @haveapi_parent_output_layout nil 7 | @haveapi_parent_output [] 8 | @before_compile HaveAPI.Meta 9 | end 10 | end 11 | 12 | defmacro __before_compile__(_env) do 13 | quote do 14 | def io(:input) do 15 | Module.concat(__MODULE__, :Input) 16 | end 17 | 18 | def io(:output) do 19 | Module.concat(__MODULE__, :Output) 20 | end 21 | 22 | def params(dir) do 23 | apply(io(dir), :params, []) 24 | 25 | rescue 26 | UndefinedFunctionError -> nil 27 | end 28 | end 29 | end 30 | 31 | def namespace, do: :_meta 32 | 33 | def add(data, meta) do 34 | Map.put(data, namespace(), meta) 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /servers/elixir/lib/haveapi/validator/confirmation.ex: -------------------------------------------------------------------------------- 1 | defmodule HaveAPI.Validator.Confirmation do 2 | use HaveAPI.Validator 3 | 4 | def name, do: :confirm 5 | 6 | def init(opts) do 7 | p = Keyword.fetch!(opts, :parameter) 8 | equal = Keyword.get(opts, :equal, true) 9 | 10 | %{ 11 | parameter: p, 12 | equal: equal, 13 | message: opts[:message] || (if equal do 14 | "must be the same as #{p}" 15 | else 16 | "must be different from #{p}" 17 | end) 18 | } 19 | end 20 | 21 | def validate(%{equal: true} = opts, v, params) do 22 | return(opts, params[ opts[:parameter] ] === v) 23 | end 24 | 25 | def validate(%{equal: false} = opts, v, params) do 26 | return(opts, params[ opts[:parameter] ] !== v) 27 | end 28 | 29 | def return(_opts, true), do: :ok 30 | def return(opts, false), do: {:error, [opts.message]} 31 | end 32 | -------------------------------------------------------------------------------- /examples/servers/ruby/activerecord_auth/db/migrate/0001_setup_database.rb: -------------------------------------------------------------------------------- 1 | class SetupDatabase < ActiveRecord::Migration 2 | def change 3 | create_table :users do |t| 4 | t.string :username, null: false, limit: 50, unique: true 5 | t.string :password, null: false, limit: 100 6 | t.boolean :is_admin, null: false, default: 0 7 | t.timestamps 8 | end 9 | 10 | create_table :auth_tokens do |t| 11 | t.references :user, null: false 12 | t.string :token, null: false, limit: 100, unique: true 13 | t.datetime :valid_to, null: true 14 | t.string :label, null: true 15 | t.integer :use_count, null: false, default: 0 16 | t.integer :lifetime, null: false 17 | t.integer :interval, null: true 18 | t.datetime :created_at, null: true 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/validators/acceptance.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/validator' 2 | 3 | module HaveAPI 4 | # Accepts a single configured value. 5 | # 6 | # Short form: 7 | # string :param, accept: 'value' 8 | # 9 | # Full form: 10 | # string :param, accept: { 11 | # value: 'value', 12 | # message: 'the error message' 13 | # } 14 | class Validators::Acceptance < Validator 15 | name :accept 16 | takes :accept 17 | 18 | def setup 19 | @value = if simple? 20 | take 21 | 22 | else 23 | take(:value) 24 | end 25 | 26 | @message = take(:message, "has to be #{@value}") 27 | end 28 | 29 | def describe 30 | { 31 | value: @value, 32 | message: @message 33 | } 34 | end 35 | 36 | def valid?(v) 37 | v == @value 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /servers/ruby/spec/validators/exclusion_spec.rb: -------------------------------------------------------------------------------- 1 | describe HaveAPI::Validators::Exclusion do 2 | shared_examples(:all) do 3 | it 'rejects a listed value' do 4 | expect(@v.valid?('one')).to be false 5 | expect(@v.valid?('two')).to be false 6 | expect(@v.valid?('three')).to be false 7 | end 8 | 9 | it 'accepts an unlisted value' do 10 | expect(@v.valid?('zero')).to be true 11 | expect(@v.valid?('four')).to be true 12 | end 13 | end 14 | 15 | context 'short form' do 16 | before(:each) do 17 | @v = HaveAPI::Validators::Exclusion.new(:exclude, %w[one two three]) 18 | end 19 | 20 | include_examples :all 21 | end 22 | 23 | context 'full form' do 24 | before(:each) do 25 | @v = HaveAPI::Validators::Exclusion.new(:exclude, { 26 | values: %w[one two three] 27 | }) 28 | end 29 | 30 | include_examples :all 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi.rb: -------------------------------------------------------------------------------- 1 | ar = Object.const_defined?(:ActiveRecord) 2 | 3 | require 'require_all' 4 | require 'active_support/inflector' 5 | require 'active_record' if ar 6 | require 'sinatra/base' 7 | require 'sinatra/cookies' 8 | require 'sinatra/activerecord' if ar 9 | require 'json' 10 | 11 | module HaveAPI 12 | module Resources; end 13 | module Actions; end 14 | end 15 | 16 | require_relative 'haveapi/params' 17 | require_rel 'haveapi/parameters/' 18 | require_rel 'haveapi/*.rb' 19 | require_rel 'haveapi/actions/*.rb' 20 | require_rel 'haveapi/resources/*.rb' 21 | require_rel 'haveapi/model_adapters/hash' 22 | require_rel 'haveapi/model_adapters/active_record' if ar 23 | require_rel 'haveapi/authentication' 24 | require_rel 'haveapi/output_formatters/base.rb' 25 | require_rel 'haveapi/output_formatters/' 26 | require_rel 'haveapi/validators/' 27 | require_rel 'haveapi/client_examples/' 28 | require_rel 'haveapi/extensions' 29 | -------------------------------------------------------------------------------- /clients/php/src/Client/Exception/ActionFailed.php: -------------------------------------------------------------------------------- 1 | response = $response; 18 | 19 | parent::__construct($message, $code, $previous); 20 | } 21 | 22 | /** 23 | * Return an array of errors. 24 | * @return array 25 | */ 26 | public function getErrors() 27 | { 28 | return $this->response->getErrors(); 29 | } 30 | 31 | /** 32 | * Return response from the API. 33 | * @return \HaveAPI\Client\Response 34 | */ 35 | public function getResponse() 36 | { 37 | return $this->response; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /clients/ruby/lib/haveapi/client/resource_instance_list.rb: -------------------------------------------------------------------------------- 1 | module HaveAPI::Client 2 | # A list of ResourceInstance objects. 3 | class ResourceInstanceList < Array 4 | def initialize(client, api, resource, action, response) 5 | super() 6 | @response = response 7 | 8 | response.response.each do |hash| 9 | self << ResourceInstance.new(client, api, resource, action: action, response: hash) 10 | end 11 | end 12 | 13 | # Return the API response that created this object. 14 | def api_response 15 | @response 16 | end 17 | 18 | def meta 19 | @response.meta 20 | end 21 | 22 | # Return the total count of items. 23 | # Note that for this method to work, the action that returns this 24 | # object list must be invoked with +meta: {count: true}+, otherwise 25 | # the object count is not sent. 26 | def total_count 27 | meta[:total_count] 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /templates/ruby/activerecord/db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # Note that this schema.rb definition is the authoritative source for your 6 | # database schema. If you need to create the application database on another 7 | # system, you should be using db:schema:load, not running all the migrations 8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 9 | # you'll amass, the slower it'll run and the greater likelihood for issues). 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(version: 1) do 14 | create_table 'dummies', force: true do |t| 15 | t.string 'name', limit: 50, null: false 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /clients/elixir/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule HaveAPI.Client.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :haveapi_client, 6 | version: "0.12.0", 7 | elixir: "~> 1.4", 8 | build_embedded: Mix.env == :prod, 9 | start_permanent: Mix.env == :prod, 10 | deps: deps()] 11 | end 12 | 13 | # Configuration for the OTP application 14 | # 15 | # Type "mix help compile.app" for more information 16 | def application do 17 | # Specify extra applications you'll use from Erlang/Elixir 18 | [extra_applications: [:logger, :httpoison]] 19 | end 20 | 21 | # Dependencies can be Hex packages: 22 | # 23 | # {:my_dep, "~> 0.3.0"} 24 | # 25 | # Or git/path repositories: 26 | # 27 | # {:my_dep, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} 28 | # 29 | # Type "mix help deps" for more examples and options 30 | defp deps do 31 | [{:httpoison, "~> 0.12"}, 32 | {:poison, "~> 3.0"}] 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /servers/ruby/doc/hooks.erb: -------------------------------------------------------------------------------- 1 | <% 2 | def render_hash(hash) 3 | return 'none' unless hash 4 | '
    ' + hash.map { |k,v| "
    #{k}
    #{v}
    " }.join('') + '
    ' 5 | end 6 | %> 7 | # Hooks 8 | <% HaveAPI::Hooks.hooks.each do |klass, hooks| %> 9 | ##<%= klass %> 10 | <% hooks.each do |name, hook| %> 11 | ### <%= name %> 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
    Description:<%= hook[:desc] %>
    Context:<%= hook[:context] || 'current' %>
    Arguments:<%= render_hash(hook[:args]) %>
    Initial value:<%= render_hash(hook[:initial]) %>
    Return value:<%= render_hash(hook[:ret]) %>
    34 | <% end %> 35 | <% end %> 36 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/authentication/token/action_result.rb: -------------------------------------------------------------------------------- 1 | module HaveAPI::Authentication 2 | module Token 3 | class ActionResult 4 | # @param complete [Boolean] 5 | # @return [Boolean] 6 | attr_accessor :complete 7 | 8 | # @param error [String] 9 | # @return [String, nil] 10 | attr_accessor :error 11 | 12 | # @param token [String] 13 | # @return [String, nil] 14 | attr_accessor :token 15 | 16 | # @param valid_to [Time] 17 | # @return [Time, nil] 18 | attr_accessor :valid_to 19 | 20 | # @param next_action [String] 21 | # @return [String, nil] 22 | attr_accessor :next_action 23 | 24 | def initialize 25 | @ok = false 26 | end 27 | 28 | def ok 29 | @ok = true 30 | self 31 | end 32 | 33 | def ok? 34 | @ok && @error.nil? 35 | end 36 | 37 | def complete? 38 | @complete ? true : false 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/validators/exclusion.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/validator' 2 | 3 | module HaveAPI 4 | # Checks that the value is not reserved. 5 | # 6 | # Short form: 7 | # string :param, exclude: %i(one two three) 8 | # 9 | # Full form: 10 | # string :param, exclude: { 11 | # values: %i(one two three), 12 | # message: 'the error message' 13 | # } 14 | # 15 | # In this case, the value could be anything but `one`, `two` or 16 | # `three`. 17 | class Validators::Exclusion < Validator 18 | name :exclude 19 | takes :exclude 20 | 21 | def setup 22 | @values = (simple? ? take : take(:values)).map! do |v| 23 | v.is_a?(::Symbol) ? v.to_s : v 24 | end 25 | 26 | @message = take(:message, '%{value} cannot be used') 27 | end 28 | 29 | def describe 30 | { 31 | values: @values, 32 | message: @message 33 | } 34 | end 35 | 36 | def valid?(v) 37 | !@values.include?(v) 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /clients/js/src/haveapi/exceptions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Thrown when protocol error/incompatibility occurs. 3 | * @class ProtocolError 4 | * @memberof HaveAPI.Client.Exceptions 5 | */ 6 | Client.Exceptions.ProtocolError = function (msg) { 7 | this.name = 'ProtocolError'; 8 | this.message = msg; 9 | } 10 | 11 | /** 12 | * Thrown when calling an action and some arguments are left unresolved. 13 | * @class UnresolvedArguments 14 | * @memberof HaveAPI.Client.Exceptions 15 | */ 16 | Client.Exceptions.UnresolvedArguments = function (action) { 17 | this.name = 'UnresolvedArguments'; 18 | this.message = "Unable to execute action '"+ action.name +"': unresolved arguments"; 19 | } 20 | 21 | /** 22 | * Thrown when trying to cancel an action that cannot be cancelled. 23 | * @class UncancelableAction 24 | * @memberof HaveAPI.Client.Exceptions 25 | */ 26 | Client.Exceptions.UncancelableAction = function (stateId) { 27 | this.name = 'UncancelableAction'; 28 | this.message = "Action state #"+ stateId +" cannot be cancelled"; 29 | } 30 | -------------------------------------------------------------------------------- /clients/php/CHANGELOG: -------------------------------------------------------------------------------- 1 | * See ../../CHANGELOG.md for newer versions 2 | 3 | * Mon Nov 27 2017 - version 0.11.0 4 | - Merged code repository 5 | 6 | * Tue Sep 12 2017 - version 0.10.0 7 | - No changes 8 | 9 | * Sat Apr 22 2017 - version 0.9.0 10 | - No changes 11 | 12 | * Fri Feb 10 2017 - version 0.8.0 13 | - Send client's IP address in HTTP header Client-IP 14 | - Protocol v1.2 15 | 16 | * Fri Nov 25 2016 - version 0.7.1 17 | - No changes 18 | 19 | * Thu Nov 24 2016 - version 0.7.0 20 | - Protocol v1.1 21 | 22 | * Tue Oct 18 2016 - version 0.6.0 23 | - Action: more meaningful message in exception UnresolvedArguments 24 | 25 | * Thu Mar 03 2016 - version 0.5.2 26 | - No changes 27 | 28 | * Fri Feb 26 2016 - version 0.5.1 29 | - No changes 30 | 31 | * Tue Feb 9 2016 - version 0.5.0 32 | - No changes 33 | 34 | * Sun Jan 24 2016 - version 0.4.2 35 | - Fixed method ActionFailed->getErrors() 36 | 37 | * Sat Jan 23 2016 - version 0.4.1 38 | - No changes 39 | 40 | * Wed Jan 20 2016 - version 0.4.0 41 | - Check protocol version 42 | -------------------------------------------------------------------------------- /clients/js/src/haveapi.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @namespace HaveAPI 3 | * @author Jakub Skokan 4 | **/ 5 | 6 | var XMLHttpRequest; 7 | 8 | if (typeof exports === 'object' && (typeof window === 'undefined' || !window.XMLHttpRequest)) { 9 | XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest; 10 | 11 | } else { 12 | XMLHttpRequest = window.XMLHttpRequest; 13 | } 14 | 15 | // Register built-in providers 16 | Authentication.registerProvider('basic', Authentication.Basic); 17 | Authentication.registerProvider('oauth2', Authentication.OAuth2); 18 | Authentication.registerProvider('token', Authentication.Token); 19 | 20 | var classes = [ 21 | 'Action', 22 | 'ActionState', 23 | 'Authentication', 24 | 'BaseResource', 25 | 'Hooks', 26 | 'Http', 27 | 'Resource', 28 | 'ResourceInstance', 29 | 'ResourceInstanceList', 30 | 'Response', 31 | 'LocalResponse', 32 | ]; 33 | 34 | for (var i = 0; i < classes.length; i++) 35 | Client[ classes[i] ] = eval(classes[i]); 36 | 37 | var HaveAPI = { 38 | Client: Client 39 | }; 40 | -------------------------------------------------------------------------------- /servers/elixir/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Haveapi.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :haveapi, 6 | version: "0.12.0", 7 | elixir: "~> 1.4", 8 | build_embedded: Mix.env == :prod, 9 | start_permanent: Mix.env == :prod, 10 | deps: deps()] 11 | end 12 | 13 | # Configuration for the OTP application 14 | # 15 | # Type "mix help compile.app" for more information 16 | def application do 17 | # Specify extra applications you'll use from Erlang/Elixir 18 | [extra_applications: [:logger]] 19 | end 20 | 21 | # Dependencies can be Hex packages: 22 | # 23 | # {:my_dep, "~> 0.3.0"} 24 | # 25 | # Or git/path repositories: 26 | # 27 | # {:my_dep, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} 28 | # 29 | # Type "mix help deps" for more examples and options 30 | defp deps do 31 | [{:cowboy, "~> 1.0.0"}, 32 | {:plug, "~> 1.0"}, 33 | {:poison, "~> 3.0"}, 34 | {:ex_doc, "~> 0.14", only: :dev, runtime: false}] 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /servers/elixir/lib/haveapi/version.ex: -------------------------------------------------------------------------------- 1 | defmodule HaveAPI.Version do 2 | defmacro __using__(_opts) do 3 | quote do 4 | @before_compile HaveAPI.Version 5 | @haveapi_version nil 6 | Module.register_attribute __MODULE__, :haveapi_auth, accumulate: true 7 | Module.register_attribute __MODULE__, :haveapi_resources, accumulate: true 8 | 9 | import HaveAPI.Version 10 | end 11 | end 12 | 13 | defmacro version(v) do 14 | quote do 15 | @haveapi_version unquote(v) 16 | end 17 | end 18 | 19 | defmacro auth_chain(chain) do 20 | quote do 21 | Enum.each(unquote(chain), &(@haveapi_auth &1)) 22 | end 23 | end 24 | 25 | defmacro resources(list) do 26 | quote do 27 | Enum.each(unquote(list), &(@haveapi_resources &1)) 28 | end 29 | end 30 | 31 | defmacro __before_compile__(_env) do 32 | quote do 33 | def version, do: @haveapi_version 34 | def auth_chain, do: Enum.reverse(@haveapi_auth) 35 | def resources, do: @haveapi_resources 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /examples/servers/ruby/activerecord_auth/lib/api.rb: -------------------------------------------------------------------------------- 1 | # ActiveRecord must be required before HaveAPI 2 | require 'active_record' 3 | require 'haveapi' 4 | 5 | module API 6 | # API resources are stored in this module 7 | module Resources; end 8 | 9 | # Authentication backends 10 | module Authentication; end 11 | 12 | # When a resource has no version set, this one will be used 13 | HaveAPI.implicit_version = '1.0' 14 | 15 | # Return API server with a default configuration 16 | # @return [HaveAPI::Server] 17 | def self.default 18 | api = HaveAPI::Server.new(Resources) 19 | 20 | # Include all detected API versions 21 | api.use_version(:all) 22 | 23 | # Register authentication backends 24 | api.auth_chain << Authentication::Basic 25 | api.auth_chain << HaveAPI::Authentication::Token.with_config(Authentication::Token) 26 | 27 | # Register routes for all actions 28 | api.mount('/') 29 | 30 | api 31 | end 32 | end 33 | 34 | require_rel '../models' 35 | require_rel 'api/resources' 36 | require_rel 'api/authentication' 37 | -------------------------------------------------------------------------------- /clients/ruby/haveapi-client.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path('lib', __dir__) 2 | $:.unshift(lib) unless $:.include?(lib) 3 | require 'haveapi/client/version' 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = 'haveapi-client' 7 | spec.version = HaveAPI::Client::VERSION 8 | spec.authors = ['Jakub Skokan'] 9 | spec.email = ['jakub.skokan@vpsfree.cz'] 10 | spec.summary = 11 | spec.description = 'Ruby API and CLI for HaveAPI' 12 | spec.homepage = '' 13 | spec.license = 'MIT' 14 | 15 | spec.required_ruby_version = '>= 3.0' 16 | 17 | spec.files = `git ls-files -z`.split("\x0") 18 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 19 | spec.require_paths = ['lib'] 20 | 21 | spec.add_dependency 'activesupport', '>= 7.0' 22 | spec.add_dependency 'highline', '~> 3.1' 23 | spec.add_dependency 'json' 24 | spec.add_dependency 'require_all', '~> 2.0.0' 25 | spec.add_dependency 'rest-client', '~> 2.1.0' 26 | spec.add_dependency 'ruby-progressbar', '~> 1.13.0' 27 | end 28 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/validators/presence.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/validator' 2 | 3 | module HaveAPI 4 | # Checks the value is present and not empty. 5 | # 6 | # Short form: 7 | # string :param, required: true 8 | # 9 | # Full form: 10 | # string :param, required: { 11 | # empty: true/false, 12 | # message: 'the error message' 13 | # } 14 | class Validators::Presence < Validator 15 | name :present 16 | takes :required, :present 17 | 18 | def setup 19 | return useless if simple? && !take 20 | 21 | @empty = take(:empty, false) 22 | @message = take( 23 | :message, 24 | @empty ? 'must be present' : 'must be present and non-empty' 25 | ) 26 | end 27 | 28 | def describe 29 | { 30 | empty: @empty, 31 | message: @message 32 | } 33 | end 34 | 35 | def valid?(v) 36 | return false if v.nil? 37 | return !v.strip.empty? if !@empty && v.is_a?(::String) 38 | 39 | # FIXME: other data types? 40 | true 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /clients/go/template/resource.go.erb: -------------------------------------------------------------------------------- 1 | package <%= package %> 2 | 3 | // Type for resource <%= resource.full_dot_name %> 4 | type <%= resource.go_type %> struct { 5 | // Pointer to client 6 | Client *Client 7 | 8 | <% resource.resources.each do |r| -%> 9 | // Resource <%= r.full_dot_name %> 10 | <%= r.go_name %> *<%= r.go_type %> 11 | <% end -%> 12 | <% resource.actions.each do |a| -%> 13 | <% a.all_names do |go_name| -%> 14 | // Action <%= a.full_dot_name %> 15 | <%= go_name %> *<%= a.go_type %> 16 | <% end -%> 17 | <% end -%> 18 | } 19 | 20 | func New<%= resource.go_type %>(client *Client) *<%= resource.go_type %> { 21 | <% resource.actions.each do |a| -%> 22 | action<%= a.go_name %> := New<%= a.go_type %>(client) 23 | <% end -%> 24 | 25 | return &<%= resource.go_type %>{ 26 | Client: client, 27 | <% resource.resources.each do |r| -%> 28 | <%= r.go_name %>: New<%= r.go_type %>(client), 29 | <% end -%> 30 | <% resource.actions.each do |a| -%> 31 | <% a.all_names do |go_name| -%> 32 | <%= go_name %>: action<%= a.go_name %>, 33 | <% end -%> 34 | <% end -%> 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /clients/go/lib/haveapi/go_client/cli.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/go_client' 2 | require 'optparse' 3 | 4 | module HaveAPI::GoClient 5 | class Cli 6 | def self.run 7 | options = { 8 | package: 'client' 9 | } 10 | 11 | parser = OptionParser.new do |opts| 12 | opts.banner = "Usage: #{$0} [options] " 13 | 14 | opts.on('--version VERSION', 'Use specified API version') do |v| 15 | options[:version] = v 16 | end 17 | 18 | opts.on('--module MODULE', 'Name of the generated Go module') do |v| 19 | options[:module] = v 20 | end 21 | 22 | opts.on('--package PKG', 'Name of the generated Go package') do |v| 23 | options[:package] = v 24 | end 25 | end 26 | 27 | parser.parse! 28 | 29 | if ARGV.length != 2 30 | warn 'Invalid arguments' 31 | puts @global_opt.help 32 | exit(false) 33 | end 34 | 35 | g = Generator.new(ARGV[0], ARGV[1], options) 36 | g.generate 37 | g.go_fmt 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/authentication/oauth2/revoke_endpoint.rb: -------------------------------------------------------------------------------- 1 | require 'rack/oauth2' 2 | 3 | module HaveAPI::Authentication 4 | module OAuth2 5 | class RevokeEndpoint < Rack::OAuth2::Server::Abstract::Handler 6 | def _call(env) 7 | @request = Request.new(env) 8 | @response = Response.new(request) 9 | super 10 | end 11 | 12 | class Request < Rack::OAuth2::Server::Abstract::Request 13 | attr_required :token 14 | attr_optional :token_type_hint 15 | 16 | def initialize(env) 17 | super 18 | @token = params['token'] 19 | @token_type_hint = params['token_type_hint'] 20 | end 21 | 22 | def unsupported_token_type!(description = nil, options = {}) 23 | raise Rack::OAuth2::Server::Abstract::BadRequest.new( 24 | :unsupported_token_type, 25 | description, 26 | options 27 | ) 28 | end 29 | end 30 | 31 | class Response < Rack::OAuth2::Server::Abstract::Response 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /clients/go/template/types.go.erb: -------------------------------------------------------------------------------- 1 | package <%= package %> 2 | 3 | import ( 4 | "strconv" 5 | ) 6 | 7 | type ProgressCallbackReturn int 8 | 9 | const ( 10 | ContinueWatching = iota 11 | StopWatching = iota 12 | ) 13 | 14 | type OperationProgressCallback func(*ActionActionStatePollOutput) ProgressCallbackReturn 15 | 16 | type BlockingOperationWatcher interface { 17 | IsBlocking() bool 18 | OperationStatus() (*ActionActionStateShowResponse, error) 19 | WaitForOperation(timeout float64) (*ActionActionStatePollResponse, error) 20 | WatchOperation(timeout float64, updateIn float64, callback OperationProgressCallback) (*ActionActionStatePollResponse, error) 21 | } 22 | 23 | func convertInt64ToString(v int64) string { 24 | return strconv.FormatInt(v, 10) 25 | } 26 | 27 | func convertFloat64ToString(v float64) string { 28 | return strconv.FormatFloat(v, 'f', -1, 64) 29 | } 30 | 31 | func convertBoolToString(v bool) string { 32 | if v { 33 | return "1" 34 | } else { 35 | return "0" 36 | } 37 | } 38 | 39 | func convertResourceToString(v int64) string { 40 | return convertInt64ToString(v) 41 | } 42 | -------------------------------------------------------------------------------- /clients/js/src/haveapi/authentication/basic.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class Basic 3 | * @classdesc Authentication provider for HTTP basic auth. 4 | * Unfortunately, this provider probably won't work in most browsers 5 | * because of their security considerations. 6 | * @memberof HaveAPI.Client.Authentication 7 | */ 8 | Authentication.Basic = function(client, opts, description) { 9 | this.client = client; 10 | this.opts = opts; 11 | }; 12 | Authentication.Basic.prototype = new Authentication.Base(); 13 | 14 | /** 15 | * @method HaveAPI.Client.Authentication.Basic#setup 16 | * @param {HaveAPI.Client~doneCallback} callback 17 | */ 18 | Authentication.Basic.prototype.setup = function(callback) { 19 | if(callback !== undefined) 20 | callback(this.client, true); 21 | }; 22 | 23 | /** 24 | * Returns an object with keys 'user' and 'password' that are used 25 | * for HTTP basic auth. 26 | * @method HaveAPI.Client.Authentication.Basic#credentials 27 | * @return {Object} credentials 28 | */ 29 | Authentication.Basic.prototype.credentials = function() { 30 | return this.opts; 31 | }; 32 | -------------------------------------------------------------------------------- /clients/ruby/lib/haveapi/cli/command.rb: -------------------------------------------------------------------------------- 1 | module HaveAPI::CLI 2 | class Command 3 | class << self 4 | attr_reader :resource, :action 5 | 6 | def cmd(resource, action = nil) 7 | @resource = resource.is_a?(::Array) ? resource : [resource] 8 | @resource.map!(&:to_s) 9 | @action = action && action.to_s 10 | 11 | Cli.register_command(self) 12 | end 13 | 14 | def args(v = nil) 15 | if v 16 | @args = v 17 | 18 | else 19 | @args 20 | end 21 | end 22 | 23 | def desc(v = nil) 24 | if v 25 | @desc = v 26 | 27 | else 28 | @desc 29 | end 30 | end 31 | 32 | def handle?(resource, action) 33 | resource == @resource && action == @action 34 | end 35 | end 36 | 37 | attr_reader :global_opts 38 | 39 | def initialize(opts, client) 40 | @global_opts = opts 41 | @api = client 42 | end 43 | 44 | def options(opts); end 45 | 46 | def exec(args) 47 | raise NotImplementedError 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /servers/elixir/lib/haveapi/parameter/output.ex: -------------------------------------------------------------------------------- 1 | defmodule HaveAPI.Parameter.Output do 2 | @spec coerce(atom, any) :: any 3 | def coerce(:string, v) when is_binary(v), do: {:ok, v} 4 | def coerce(:string, v) do 5 | if is_nil(String.Chars.impl_for(v)) do 6 | {:error, "#{inspect(v)} does not implement String.Chars protocol"} 7 | 8 | else 9 | {:ok, to_string(v)} 10 | end 11 | end 12 | 13 | def coerce(:text, v), do: coerce(:string, v) 14 | 15 | def coerce(:integer, v) when is_integer(v), do: {:ok, v} 16 | 17 | def coerce(:float, v) when is_float(v), do: {:ok, v} 18 | 19 | def coerce(:boolean, v) when is_boolean(v), do: {:ok, v} 20 | 21 | def coerce(:datetime, %DateTime{} = v), do: {:ok, DateTime.to_iso8601(v)} 22 | def coerce(:datetime, %Date{} = v), do: {:ok, Date.to_iso8601(v)} 23 | def coerce(:datetime, %NaiveDateTime{} = v) do 24 | {:ok, dt} = DateTime.from_naive(v, "Etc/UTC") 25 | {:ok, DateTime.to_iso8601(dt)} 26 | end 27 | 28 | def coerce(:custom, v), do: {:ok, v} 29 | 30 | def coerce(type, v), do: {:error, "#{inspect(v)} is not #{type}"} 31 | end 32 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/validators/format.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/validator' 2 | 3 | module HaveAPI 4 | # Checks that the value is or is not in specified format. 5 | # 6 | # Short form: 7 | # string :param, format: /^[a-z0-9]+$/ 8 | # 9 | # Full form: 10 | # string :param, format: { 11 | # rx: /^[a-z0-9]+$/, 12 | # match: true/false, 13 | # message: 'the error message' 14 | # } 15 | class Validators::Format < Validator 16 | name :format 17 | takes :format 18 | 19 | def setup 20 | @rx = simple? ? take : take(:rx) 21 | @match = take(:match, true) 22 | @desc = take(:desc) 23 | @message = take(:message, @desc || '%{value} is not in a valid format') 24 | end 25 | 26 | def describe 27 | { 28 | rx: @rx.source, 29 | match: @match, 30 | description: @desc, 31 | message: @message 32 | } 33 | end 34 | 35 | def valid?(v) 36 | if @match 37 | @rx.match(v) ? true : false 38 | 39 | else 40 | @rx.match(v) ? false : true 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /servers/ruby/spec/common_spec.rb: -------------------------------------------------------------------------------- 1 | describe HaveAPI::Common do 2 | class Test1 < HaveAPI::Common 3 | has_attr :attr1 4 | has_attr :attr2, 42 5 | end 6 | 7 | it 'defines attributes' do 8 | expect(Test1.attr1).to be_nil 9 | expect(Test1.attr2).to eq(42) 10 | end 11 | 12 | class Test2 < HaveAPI::Common 13 | has_attr :attr1 14 | has_attr :attr2, 42 15 | end 16 | 17 | it 'sets attributes' do 18 | Test2.attr1 'val1' 19 | Test2.attr2 663 20 | 21 | expect(Test2.attr1).to eq('val1') 22 | expect(Test2.attr2).to eq(663) 23 | end 24 | 25 | class Test3 < HaveAPI::Common 26 | has_attr :attr1 27 | has_attr :attr2, 42 28 | has_attr :attr3 29 | 30 | attr1 'foo' 31 | attr2 :bar 32 | 33 | def self.inherited(subclass) 34 | super 35 | inherit_attrs(subclass) 36 | end 37 | end 38 | 39 | class SubTest3 < Test3 40 | attr3 'bar' 41 | end 42 | 43 | it 'inherites attributes' do 44 | expect(SubTest3.attr1).to eq('foo') 45 | expect(SubTest3.attr2).to eq(:bar) 46 | expect(SubTest3.attr3).to eq('bar') 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /clients/elixir/lib/haveapi/client/protocol.ex: -------------------------------------------------------------------------------- 1 | defmodule HaveAPI.Client.Protocol do 2 | alias HaveAPI.Client 3 | alias HaveAPI.Client.Http 4 | 5 | def describe(%Client.Conn{version: nil} = conn) do 6 | res = response(Http.options(conn.url, params: [{"describe", "default"}])) 7 | res.body["response"] 8 | end 9 | 10 | def describe(conn) do 11 | res = [conn.url, "v#{conn.version}"] 12 | |> Path.join 13 | |> Client.Conn.ensure_trailslash 14 | |> Http.options([]) 15 | |> response 16 | 17 | res.body["response"] 18 | end 19 | 20 | def execute(req) do 21 | data = Http.request( 22 | req.conn.action_desc["method"], 23 | Path.join([req.conn.url, req.path]), 24 | request_body(req.body), 25 | req.headers, 26 | params: req.query_params 27 | ) |> response() 28 | 29 | Client.Response.new(req.conn, data) 30 | end 31 | 32 | defp request_body(""), do: "" 33 | defp request_body(body), do: Poison.encode!(body) 34 | 35 | defp response({:ok, response}) do 36 | %{response | body: Poison.decode!(response.body)} 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /servers/ruby/haveapi.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path('lib', __dir__) 2 | $:.unshift(lib) unless $:.include?(lib) 3 | require 'haveapi/version' 4 | 5 | Gem::Specification.new do |s| 6 | s.name = 'haveapi' 7 | s.version = HaveAPI::VERSION 8 | s.summary = 9 | s.description = 'Framework for creating self-describing APIs' 10 | s.authors = 'Jakub Skokan' 11 | s.email = 'jakub.skokan@vpsfree.cz' 12 | s.files = `git ls-files -z`.split("\x0") + Dir.glob('doc/*') 13 | s.license = 'MIT' 14 | 15 | s.required_ruby_version = ">= #{File.read('../../.ruby-version').strip}" 16 | 17 | s.add_dependency 'activesupport', '>= 7.1' 18 | s.add_dependency 'haveapi-client', '~> 0.26.0' 19 | s.add_dependency 'json' 20 | s.add_dependency 'mail' 21 | s.add_dependency 'nesty', '~> 1.0' 22 | s.add_dependency 'rack-oauth2', '~> 2.2.0' 23 | s.add_dependency 'rake' 24 | s.add_dependency 'redcarpet', '~> 3.6' 25 | s.add_dependency 'require_all', '~> 2.0.0' 26 | s.add_dependency 'sinatra', '~> 4.0' 27 | s.add_dependency 'sinatra-contrib', '~> 4.0' 28 | s.add_dependency 'tilt', '~> 2.4' 29 | end 30 | -------------------------------------------------------------------------------- /clients/js/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Jakub Skokan 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /clients/php/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Jakub Skokan 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /clients/ruby/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Jakub Skokan 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /servers/ruby/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Jakub Skokan 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/validators/confirmation.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/validator' 2 | 3 | module HaveAPI 4 | # Checks that two parameters are equal or not equal. 5 | # 6 | # Short form: 7 | # string :param, confirm: :other_parameter 8 | # 9 | # Full form: 10 | # string :param, confirm: { 11 | # param: :other_parameter, 12 | # equal: true/false, 13 | # message: 'the error message' 14 | # } 15 | # 16 | # `equal` defaults to `true`. 17 | class Validators::Confirmation < Validator 18 | name :confirm 19 | takes :confirm 20 | 21 | def setup 22 | @param = simple? ? take : take(:param) 23 | @equal = take(:equal, true) 24 | @message = take( 25 | :message, 26 | @equal ? "must be the same as #{@param}" : "must be different from #{@param}" 27 | ) 28 | end 29 | 30 | def describe 31 | { 32 | equal: @equal ? true : false, 33 | parameter: @param, 34 | message: @message 35 | } 36 | end 37 | 38 | def valid?(v) 39 | if @equal 40 | v == params[@param] 41 | 42 | else 43 | v != params[@param] 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /servers/elixir/lib/haveapi/action/create.ex: -------------------------------------------------------------------------------- 1 | defmodule HaveAPI.Action.Create do 2 | use HaveAPI.Action 3 | 4 | @callback create(map) :: any 5 | 6 | method :post 7 | route "" 8 | aliases [:new] 9 | 10 | meta :global do 11 | output do 12 | custom :url_params 13 | boolean :resolved 14 | end 15 | end 16 | 17 | def use_template do 18 | quote do 19 | @behaviour unquote(__MODULE__) 20 | 21 | def exec(req) do 22 | unquote(__MODULE__).exec(__MODULE__, req) 23 | end 24 | end 25 | end 26 | 27 | def exec(mod, req) do 28 | v = mod.create(req) 29 | res = HaveAPI.Action.Output.build(req, v) 30 | 31 | if res.status do 32 | add_local_metadata(req, res) 33 | 34 | else 35 | res 36 | end 37 | end 38 | 39 | # TODO: mention/fix that we need output parameter `id` to be present 40 | def add_local_metadata(req, res) do 41 | if res.output[:id] do 42 | %{res | meta: %{ 43 | url_params: ( 44 | req.params |> Keyword.delete_first(:glob) |> Keyword.values 45 | ) ++ [res.output[:id]], 46 | resolved: true 47 | }} 48 | 49 | else 50 | res 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /servers/ruby/spec/validators/inclusion_spec.rb: -------------------------------------------------------------------------------- 1 | describe HaveAPI::Validators::Inclusion do 2 | shared_examples(:all) do 3 | it 'accepts a listed value' do 4 | expect(@v.valid?('one')).to be true 5 | expect(@v.valid?('two')).to be true 6 | expect(@v.valid?('three')).to be true 7 | end 8 | 9 | it 'rejects an unlisted value' do 10 | expect(@v.valid?('zero')).to be false 11 | expect(@v.valid?('four')).to be false 12 | end 13 | end 14 | 15 | [ 16 | %w[one two three], 17 | { 18 | one: 'Fancy one', 19 | two: 'Fancy two', 20 | three: 'Fancy three' 21 | } 22 | ].each do |include| 23 | context "with include as a '#{include.class}'" do 24 | context 'short form' do 25 | before(:each) do 26 | @v = HaveAPI::Validators::Inclusion.new(:include, %w[one two three]) 27 | end 28 | 29 | include_examples :all 30 | end 31 | 32 | context 'full form' do 33 | before(:each) do 34 | @v = HaveAPI::Validators::Inclusion.new(:include, { 35 | values: %w[one two three] 36 | }) 37 | end 38 | 39 | include_examples :all 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/spec/mock_action.rb: -------------------------------------------------------------------------------- 1 | module HaveAPI::Spec 2 | class MockAction 3 | def initialize(test, server, action, path, v) 4 | @test = test 5 | @server = server 6 | @action = action 7 | @path = path 8 | @v = v 9 | end 10 | 11 | def call(input, user: nil, &) 12 | action = @action.new(nil, @v, input, nil, HaveAPI::Context.new( 13 | @server, 14 | version: @v, 15 | action: @action, 16 | path: @path, 17 | params: input, 18 | user:, 19 | endpoint: true 20 | )) 21 | 22 | unless action.authorized?(user) 23 | raise 'Access denied. Insufficient permissions.' 24 | end 25 | 26 | status, data, errors = action.safe_exec 27 | raise(data || 'action failed') unless status 28 | 29 | action.instance_exec(@test, &) 30 | data 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /servers/elixir/test/haveapi/validator_test.exs: -------------------------------------------------------------------------------- 1 | defmodule HaveAPI.ValidatorTest do 2 | use ExUnit.Case 3 | use HaveAPI.Test 4 | 5 | defmodule Api do 6 | use HaveAPI.Builder 7 | 8 | defmodule MyResource do 9 | use HaveAPI.Resource 10 | 11 | defmodule Template do 12 | use HaveAPI.Action 13 | 14 | auth false 15 | method :post 16 | route "%{action}" 17 | end 18 | 19 | defmodule PresenceOn do 20 | use Template 21 | 22 | input do 23 | string :str, validate: [ 24 | required: true 25 | ] 26 | end 27 | 28 | def exec(_req), do: :ok 29 | end 30 | 31 | actions [ 32 | PresenceOn, 33 | ] 34 | end 35 | 36 | version "1.0" do 37 | resources [MyResource] 38 | end 39 | 40 | mount "/" 41 | end 42 | 43 | test "validators are in action description" do 44 | conn = call_api(Api, :options, "/v1.0/myresource/presenceon/method=post") 45 | 46 | assert conn.status === 200 47 | assert get_in( 48 | conn.resp_body, 49 | ~w(response 50 | input 51 | parameters 52 | str 53 | validators 54 | present)) |> is_map 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /servers/elixir/config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for 9 | # 3rd-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure for your application as: 12 | # 13 | # config :haveapi, key: :value 14 | # 15 | # And access this configuration in your application as: 16 | # 17 | # Application.get_env(:haveapi, :key) 18 | # 19 | # Or configure a 3rd-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | # 30 | # import_config "#{Mix.env}.exs" 31 | -------------------------------------------------------------------------------- /servers/elixir/lib/haveapi/authentication/basic.ex: -------------------------------------------------------------------------------- 1 | defmodule HaveAPI.Authentication.Basic do 2 | @callback find_user(%Plug.Conn{}, String.t, String.t) :: any 3 | 4 | defmacro __using__(_opts) do 5 | quote do 6 | @behaviour HaveAPI.Authentication 7 | @behaviour unquote(__MODULE__) 8 | 9 | def name, do: :basic 10 | 11 | def describe(_ctx) do 12 | %{ 13 | description: "Authentication using HTTP basic. Username and password is passed " <> 14 | "via HTTP header. Its use is forbidden from web browsers." 15 | } 16 | end 17 | 18 | def authenticate(conn) do 19 | header = Enum.find( 20 | conn.req_headers, 21 | fn {k,v} -> k == "authorization" end 22 | ) 23 | 24 | if header do 25 | {_, auth} = header 26 | [_, auth] = String.split(auth, ~r{\s}, trim: true) 27 | [user, password] = auth |> Base.decode64! |> String.split(":") 28 | 29 | find_user(conn, user, password) 30 | 31 | else 32 | nil 33 | end 34 | 35 | rescue ArgumentError -> 36 | nil 37 | end 38 | 39 | def resources, do: [] 40 | 41 | def required_headers, do: [] 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /servers/elixir/lib/haveapi/resource.ex: -------------------------------------------------------------------------------- 1 | defmodule HaveAPI.Resource do 2 | defmacro __using__(_opts) do 3 | quote do 4 | import HaveAPI.Resource 5 | @before_compile HaveAPI.Resource 6 | Module.register_attribute __MODULE__, :haveapi_actions, accumulate: true 7 | Module.register_attribute __MODULE__, :haveapi_resources, accumulate: true 8 | @haveapi_route nil 9 | end 10 | end 11 | 12 | defmacro resource_route(v) do 13 | quote do: @haveapi_route unquote(v) 14 | end 15 | 16 | defmacro resources(list) do 17 | quote do 18 | Enum.each(unquote(list), &(@haveapi_resources &1)) 19 | end 20 | end 21 | 22 | defmacro actions(acts) do 23 | quote do 24 | Enum.each(unquote(acts), &(@haveapi_actions &1)) 25 | end 26 | end 27 | 28 | defmacro __before_compile__(_env) do 29 | quote do 30 | def resources do 31 | @haveapi_resources 32 | end 33 | 34 | def actions do 35 | @haveapi_actions 36 | end 37 | 38 | def route do 39 | _route() || name() 40 | end 41 | 42 | def name do 43 | Module.split(__MODULE__) |> List.last |> String.downcase 44 | end 45 | 46 | defp _route, do: @haveapi_route 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /clients/elixir/config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for 9 | # 3rd-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure for your application as: 12 | # 13 | # config :haveapi_client, key: :value 14 | # 15 | # And access this configuration in your application as: 16 | # 17 | # Application.get_env(:haveapi_client, :key) 18 | # 19 | # Or configure a 3rd-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | # 30 | # import_config "#{Mix.env}.exs" 31 | -------------------------------------------------------------------------------- /clients/go/lib/haveapi/go_client/erb_template.rb: -------------------------------------------------------------------------------- 1 | require 'erb' 2 | require 'fileutils' 3 | 4 | module HaveAPI::GoClient 5 | class ErbTemplate 6 | def self.render(name, vars) 7 | t = new(name, vars) 8 | t.render 9 | end 10 | 11 | def self.render_to(name, vars, path) 12 | File.write("#{path}.new", render(name, vars)) 13 | File.rename("#{path}.new", path) 14 | end 15 | 16 | def self.render_to_if_changed(name, vars, path) 17 | tmp_path = "#{path}.new" 18 | File.write(tmp_path, render(name, vars)) 19 | 20 | if !File.exist?(path) || !FileUtils.identical?(path, tmp_path) 21 | File.rename(tmp_path, path) 22 | 23 | else 24 | File.unlink(tmp_path) 25 | end 26 | end 27 | 28 | def initialize(name, vars) 29 | @_tpl = ERB.new(File.new(HaveAPI::GoClient.tpl(name)).read, trim_mode: '-') 30 | 31 | vars.each do |k, v| 32 | if v.is_a?(Proc) 33 | define_singleton_method(k, &v) 34 | elsif v.is_a?(Method) 35 | define_singleton_method(k) { |*args| v.call(*args) } 36 | else 37 | define_singleton_method(k) { v } 38 | end 39 | end 40 | end 41 | 42 | def render 43 | @_tpl.result(binding) 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /clients/ruby/lib/haveapi/client/validator.rb: -------------------------------------------------------------------------------- 1 | module HaveAPI::Client 2 | module Validators; end 3 | 4 | class Validator 5 | class << self 6 | def name(v) 7 | Validator.register(v, self) 8 | end 9 | 10 | def register(name, klass) 11 | @validators ||= {} 12 | @validators[name] = klass 13 | end 14 | 15 | def validate(validators, param, other_params) 16 | ret = [] 17 | 18 | validators.each do |name, desc| 19 | raise "unsupported validator '#{name}'" if @validators[name].nil? 20 | 21 | v = @validators[name].new(desc, param, other_params) 22 | ret.concat(v.errors) unless v.valid? 23 | end 24 | 25 | ret.empty? ? true : ret 26 | end 27 | end 28 | 29 | attr_reader :value, :params 30 | 31 | def initialize(opts, value, other_params) 32 | @opts = opts 33 | @value = value 34 | @params = other_params.params 35 | end 36 | 37 | def errors 38 | @errors || [format(opts[:message], value: value)] 39 | end 40 | 41 | def valid? 42 | raise NotImplementedError 43 | end 44 | 45 | protected 46 | 47 | attr_reader :opts 48 | 49 | def error(e) 50 | @errors ||= [] 51 | @errors << e 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /clients/go/lib/haveapi/go_client/input_output.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/go_client/utils' 2 | 3 | module HaveAPI::GoClient 4 | class InputOutput 5 | include Utils 6 | 7 | # @return [Action] 8 | attr_reader :action 9 | 10 | # @return [Symbol] 11 | attr_reader :role 12 | 13 | # @return [Symbol] 14 | attr_reader :direction 15 | 16 | # @return [String] 17 | attr_reader :layout 18 | 19 | # @return [String] 20 | attr_reader :namespace 21 | 22 | # @return [Array] 23 | attr_reader :parameters 24 | 25 | # @return [String] 26 | attr_reader :go_type 27 | 28 | # @return [String] 29 | attr_reader :go_namespace 30 | 31 | def initialize(action, role, direction, desc, prefix: nil) 32 | @action = action 33 | @role = role 34 | @direction = direction 35 | @layout = desc[:layout] 36 | @namespace = desc[:namespace] 37 | @parameters = desc[:parameters].map do |k, v| 38 | Parameter.new(role, direction, self, k.to_s, v) 39 | end.compact.sort! 40 | @go_type = action.go_type + (prefix || '') + direction.to_s.capitalize 41 | @go_namespace = camelize(desc[:namespace]) 42 | end 43 | 44 | def resolve_associations 45 | parameters.each(&:resolve) 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /clients/elixir/lib/haveapi/client/authentication.ex: -------------------------------------------------------------------------------- 1 | defmodule HaveAPI.Client.Authentication do 2 | alias HaveAPI.Client 3 | 4 | @callback setup(conn :: map, opts :: list) :: {:ok, map} | {:error, String.t} 5 | @callback authenticate(request :: map, opts :: any) :: map 6 | @callback logout(conn :: map, opts :: any) :: :ok | {:error, String.t} 7 | 8 | @enforce_keys [:module, :opts] 9 | defstruct [:module, :opts] 10 | 11 | def new(module, opts) do 12 | %__MODULE__{module: module, opts: opts} 13 | end 14 | 15 | def setup(conn, module, opts) do 16 | case apply(module, :setup, [conn, opts]) do 17 | {:ok, auth_opts} -> 18 | {:ok, %{conn | auth: new(module, auth_opts)}} 19 | 20 | {:error, msg} -> 21 | {:error, msg} 22 | end 23 | end 24 | 25 | def authenticate(%Client.Request{conn: %Client.Conn{auth: nil}} = req), do: req 26 | 27 | def authenticate(req) do 28 | apply(req.conn.auth.module, :authenticate, [req, req.conn.auth.opts]) 29 | end 30 | 31 | def logout(%Client.Conn{auth: nil} = conn), do: conn 32 | 33 | def logout(conn) do 34 | case apply(conn.auth.module, :logout, [conn, conn.auth.opts]) do 35 | :ok -> 36 | %{conn | auth: nil} 37 | 38 | {:error, msg} -> 39 | {:error, msg} 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /servers/ruby/spec/validators/confirmation_spec.rb: -------------------------------------------------------------------------------- 1 | describe HaveAPI::Validators::Confirmation do 2 | shared_examples(:all) do 3 | it 'accepts the same value' do 4 | expect(@v.validate('foo', { other_param: 'foo' })).to be true 5 | end 6 | 7 | it 'rejects a different value' do 8 | expect(@v.validate('bar', { other_param: 'foo' })).to be false 9 | end 10 | end 11 | 12 | context 'short form' do 13 | before(:each) do 14 | @v = HaveAPI::Validators::Confirmation.new(:confirm, :other_param) 15 | end 16 | 17 | include_examples :all 18 | end 19 | 20 | context 'full form' do 21 | before(:each) do 22 | @v = HaveAPI::Validators::Confirmation.new(:confirm, { 23 | param: :other_param 24 | }) 25 | end 26 | 27 | include_examples :all 28 | end 29 | 30 | context 'with equal = false' do 31 | before(:each) do 32 | @v = HaveAPI::Validators::Confirmation.new(:confirm, { 33 | param: :other_param, 34 | equal: false 35 | }) 36 | end 37 | 38 | it 'rejects the same value' do 39 | expect(@v.validate('foo', { other_param: 'foo' })).to be false 40 | end 41 | 42 | it 'accepts a different value' do 43 | expect(@v.validate('bar', { other_param: 'foo' })).to be true 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /clients/js/src/haveapi/resource.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class Resource 3 | * @memberof HaveAPI.Client 4 | */ 5 | function Resource (client, parent, name, description, args) { 6 | this._private = { 7 | client: client, 8 | parent: parent, 9 | name: name, 10 | description: description, 11 | args: args 12 | }; 13 | 14 | this.attachResources(description, args); 15 | this.attachActions(description, args); 16 | 17 | var that = this; 18 | var fn = function() { 19 | return new Resource( 20 | that._private.client, 21 | that._private.parent, 22 | that._private.name, 23 | that._private.description, 24 | that._private.args.concat(Array.prototype.slice.call(arguments)) 25 | ); 26 | }; 27 | fn.__proto__ = this; 28 | 29 | return fn; 30 | }; 31 | 32 | Resource.prototype = new BaseResource(); 33 | 34 | // Unused 35 | Resource.prototype.applyArguments = function(args) { 36 | for(var i = 0; i < args.length; i++) { 37 | this._private.args.push(args[i]); 38 | } 39 | 40 | return this; 41 | }; 42 | 43 | /** 44 | * Return a new, empty resource instance. 45 | * @method HaveAPI.Client.Resource#new 46 | * @return {HaveAPI.Client.ResourceInstance} resource instance 47 | */ 48 | Resource.prototype.new = function() { 49 | return new Client.ResourceInstance(this.client, this.parent, this.create, null, false); 50 | }; 51 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/validators/inclusion.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/validator' 2 | 3 | module HaveAPI 4 | # Checks that the value is from given set of allowed values. 5 | # 6 | # Short form: 7 | # string :param, choices: %i(one two three) 8 | # 9 | # Full form: 10 | # string :param, choices: { 11 | # values: %i(one two three), 12 | # message: 'the error message' 13 | # } 14 | # 15 | # Option `choices` is an alias to `include`. 16 | class Validators::Inclusion < Validator 17 | name :include 18 | takes :choices, :include 19 | 20 | def setup 21 | values = simple? ? take : take(:values) 22 | 23 | if values.is_a?(::Hash) 24 | @values = {} 25 | 26 | values.each do |k, v| 27 | @values[k.is_a?(::Symbol) ? k.to_s : k] = v 28 | end 29 | 30 | else 31 | @values = values.map { |v| v.is_a?(::Symbol) ? v.to_s : v } 32 | end 33 | 34 | @message = take(:message, '%{value} cannot be used') 35 | end 36 | 37 | def describe 38 | { 39 | values: @values, 40 | message: @message 41 | } 42 | end 43 | 44 | def valid?(v) 45 | if @values.is_a?(::Hash) 46 | @values.has_key?(v) 47 | 48 | else 49 | @values.include?(v) 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /clients/go/template/authentication/oauth2.go.erb: -------------------------------------------------------------------------------- 1 | package <%= package %> 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | type OAuth2Auth struct { 9 | // The authentication token 10 | AccessToken string 11 | } 12 | 13 | func (auth *OAuth2Auth) Authenticate(request *http.Request) { 14 | request.Header.Set("<%= auth.http_header %>", auth.AccessToken) 15 | } 16 | 17 | // SetExistingTokenAuth will use a previously acquired access token 18 | func (client *Client) SetExistingOAuth2Auth(accessToken string) { 19 | client.Authentication = &OAuth2Auth{ 20 | AccessToken: accessToken, 21 | } 22 | } 23 | 24 | // RevokeAuthToken will revoke the access token and remove authentication 25 | // from the client 26 | func (client *Client) RevokeAccessToken() error { 27 | httpClient := &http.Client{} 28 | 29 | req, err := http.NewRequest("POST", "<%= auth.revoke_url %>", nil) 30 | 31 | if err != nil { 32 | return err 33 | } 34 | 35 | if client.Authentication != nil { 36 | client.Authentication.Authenticate(req) 37 | } 38 | 39 | resp, err := httpClient.Do(req) 40 | 41 | if err != nil { 42 | return err 43 | } 44 | 45 | defer resp.Body.Close() 46 | 47 | if resp.StatusCode != 200 { 48 | return fmt.Errorf("Unable to revoke access token, HTTP %v", resp.StatusCode) 49 | } 50 | 51 | client.Authentication = nil 52 | return nil 53 | } 54 | -------------------------------------------------------------------------------- /servers/ruby/spec/validators/presence_spec.rb: -------------------------------------------------------------------------------- 1 | describe HaveAPI::Validators::Presence do 2 | shared_examples(:all) do 3 | it 'accepts a present value' do 4 | expect(@v.valid?('foo')).to be true 5 | end 6 | 7 | it 'rejects a missing or empty value' do 8 | expect(@v.valid?(nil)).to be false 9 | expect(@v.valid?('')).to be false 10 | expect(@v.valid?(" \t" * 4)).to be false 11 | end 12 | end 13 | 14 | context 'with empty = true' do 15 | context 'in short form' do 16 | before(:each) do 17 | @v = HaveAPI::Validators::Presence.new(:required, true) 18 | end 19 | 20 | include_examples :all 21 | end 22 | 23 | context 'in full form' do 24 | before(:each) do 25 | @v = HaveAPI::Validators::Presence.new(:required, {}) 26 | end 27 | 28 | include_examples :all 29 | end 30 | end 31 | 32 | context 'with empty = false' do 33 | before(:each) do 34 | @v = HaveAPI::Validators::Presence.new(:required, { empty: true }) 35 | end 36 | 37 | it 'accepts a present value' do 38 | expect(@v.valid?('foo')).to be true 39 | end 40 | 41 | it 'rejects a missing or an empty value' do 42 | expect(@v.valid?(nil)).to be false 43 | expect(@v.valid?('')).to be true 44 | expect(@v.valid?(" \t" * 4)).to be true 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/common.rb: -------------------------------------------------------------------------------- 1 | module HaveAPI 2 | class Common 3 | class << self 4 | attr_accessor :custom_attrs 5 | 6 | def has_attr(name, default = nil) 7 | @custom_attrs ||= [] 8 | @custom_attrs << name 9 | 10 | instance_variable_set("@#{name}", default) 11 | 12 | self.class.send(:define_method, name) do |value = nil| 13 | if value.nil? 14 | instance_variable_get("@#{name}") 15 | else 16 | instance_variable_set("@#{name}", value) 17 | end 18 | end 19 | end 20 | 21 | # Called before subclass defines it's attributes (before has_attr or custom 22 | # attr setting), so copy defaults from parent and let it override it. 23 | def inherit_attrs(subclass) 24 | return unless @custom_attrs 25 | 26 | subclass.custom_attrs = [] 27 | 28 | @custom_attrs.each do |attr| 29 | # puts "#{subclass}: Inherit #{attr} = #{instance_variable_get("@#{attr}")}" 30 | subclass.method(attr).call(instance_variable_get("@#{attr}")) 31 | subclass.custom_attrs << attr 32 | end 33 | end 34 | 35 | def check_build(msg) 36 | yield 37 | rescue StandardError => e 38 | raise BuildError.new(msg, e) 39 | end 40 | end 41 | 42 | has_attr :obj_type 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /clients/ruby/lib/haveapi/cli/example_formatter.rb: -------------------------------------------------------------------------------- 1 | module HaveAPI::CLI 2 | module ExampleFormatter 3 | def self.format_examples(cli, action, out = $>) 4 | action.examples.each do |example| 5 | out << ' > ' << example[:title] << ":\n" unless example[:title].empty? 6 | 7 | # request 8 | out << "$ #{$0} #{action.resource_path.join('.')} #{action.name}" 9 | 10 | params = example[:request] 11 | 12 | if params 13 | out << ' --' unless params.empty? 14 | 15 | params.each do |k, v| 16 | desc = action.param_description(:input, k) 17 | next unless desc 18 | 19 | out << ' ' << example_param(k, v, desc) 20 | end 21 | end 22 | 23 | out << "\n" 24 | 25 | # response 26 | cli.format_output( 27 | action, 28 | { action.namespace(:output).to_sym => example[:response] }, 29 | out 30 | ) 31 | end 32 | end 33 | 34 | def self.example_param(name, value, desc) 35 | option = name.to_s.dasherize 36 | 37 | case desc[:type] 38 | when 'Boolean' 39 | value ? "--#{option}" : "--no-#{option}" 40 | 41 | else 42 | "--#{option} #{example_value(value)}" 43 | end 44 | end 45 | 46 | def self.example_value(v) 47 | v.is_a?(String) && (v.empty? || v.index(' ')) ? "\"#{v}\"" : v 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /servers/elixir/lib/haveapi/action/input.ex: -------------------------------------------------------------------------------- 1 | defmodule HaveAPI.Action.Input do 2 | def fetch_path_parameters(req) do 3 | %{req | params: Enum.map( 4 | req.conn.path_params, 5 | fn {k,v} -> {String.to_atom(k), v} end 6 | )} 7 | end 8 | 9 | def fetch_parameters(req) do 10 | if req.context.action.method == :get do 11 | do_fetch_parameters(req, req.conn.query_params) 12 | 13 | else 14 | do_fetch_parameters(req, req.conn.body_params) 15 | end 16 | end 17 | 18 | defp do_fetch_parameters(req, data) do 19 | with {:ok, input} <- do_fetch_parameters(req, data, :input), 20 | {:ok, meta} <- do_fetch_parameters(req, data, :meta) do 21 | {:ok, %{req | input: input, meta: meta}} 22 | 23 | else 24 | {:error, errors} -> 25 | {:error, "Input parameters not valid", errors: errors} 26 | end 27 | end 28 | 29 | defp do_fetch_parameters(req, data, :input) do 30 | HaveAPI.Parameters.extract( 31 | req.context.action.params(:input), 32 | req.context.resource.name, 33 | data 34 | ) 35 | end 36 | 37 | defp do_fetch_parameters(req, data, :meta) do 38 | if req.context.action.has_meta?(:global) do 39 | HaveAPI.Parameters.extract( 40 | req.context.action.meta(:global).params(:input), 41 | Atom.to_string(HaveAPI.Meta.namespace), 42 | data 43 | ) 44 | 45 | else 46 | {:ok, nil} 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/metadata.rb: -------------------------------------------------------------------------------- 1 | module HaveAPI 2 | class Metadata 3 | def self.namespace 4 | :_meta 5 | end 6 | 7 | def self.describe 8 | { 9 | namespace: 10 | } 11 | end 12 | 13 | class ActionMetadata 14 | attr_writer :action 15 | 16 | def clone 17 | m = self.class.new 18 | m.action = @action 19 | m.instance_variable_set(:@input, @input && @input.clone) 20 | m.instance_variable_set(:@output, @output && @output.clone) 21 | m 22 | end 23 | 24 | def input(layout = :hash, &block) 25 | if block 26 | @input ||= Params.new(:input, @action) 27 | @input.action = @action 28 | @input.layout = layout 29 | @input.namespace = false 30 | @input.add_block(block) 31 | else 32 | @input 33 | end 34 | end 35 | 36 | def output(layout = :hash, &block) 37 | if block 38 | @output ||= Params.new(:output, @action) 39 | @output.action = @action 40 | @output.layout = layout 41 | @output.namespace = false 42 | @output.add_block(block) 43 | else 44 | @output 45 | end 46 | end 47 | 48 | def describe(context) 49 | { 50 | input: @input && @input.describe(context), 51 | output: @output && @output.describe(context) 52 | } 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/output_formatter.rb: -------------------------------------------------------------------------------- 1 | module HaveAPI 2 | module OutputFormatters 3 | end 4 | 5 | class OutputFormatter 6 | class << self 7 | attr_reader :formatters 8 | 9 | def register(klass) 10 | @formatters ||= [] 11 | @formatters << klass 12 | end 13 | end 14 | 15 | def supports?(types) 16 | @formatter = nil 17 | 18 | if types.empty? 19 | return @formatter = self.class.formatters.first.new 20 | end 21 | 22 | types.each do |type| 23 | self.class.formatters.each do |f| 24 | if f.handle?(type) 25 | @formatter = f.new 26 | break 27 | end 28 | end 29 | end 30 | 31 | !@formatter.nil? 32 | end 33 | 34 | def format(status, response, message = nil, errors = nil, version: true) 35 | @formatter.format(header(status, response, message, errors, version)) 36 | end 37 | 38 | def error(msg) 39 | @formatter.format(header(false, nil, msg)) 40 | end 41 | 42 | def content_type 43 | @formatter.content_type 44 | end 45 | 46 | protected 47 | 48 | def header(status, response, message = nil, errors = nil, version = nil) 49 | ret = {} 50 | ret[:version] = HaveAPI::PROTOCOL_VERSION if version 51 | ret.update({ 52 | status:, 53 | response:, 54 | message:, 55 | errors: 56 | }) 57 | ret 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/authentication/token/action_config.rb: -------------------------------------------------------------------------------- 1 | module HaveAPI::Authentication 2 | module Token 3 | class ActionConfig 4 | # @param block [Proc] 5 | # @param opts [Hash] 6 | # @option opts [Boolean] :input 7 | # @option opts [Boolean] :handle 8 | def initialize(block, opts = {}) 9 | @block = block 10 | @opts = with_defaults(opts) 11 | update(block) 12 | end 13 | 14 | # @param block [Proc] 15 | def update(block) 16 | instance_exec(&block) 17 | end 18 | 19 | # Configure input parameters in the context of {HaveAPI::Params} 20 | def input(&block) 21 | if block && check!(:input) 22 | @input = block 23 | else 24 | @input 25 | end 26 | end 27 | 28 | # Handle the action 29 | # @yieldparam request [ActionRequest] 30 | # @yieldparam result [ActionResult] 31 | # @yieldreturn [ActionResult] 32 | def handle(&block) 33 | if block && check!(:handle) 34 | @handle = block 35 | else 36 | @handle 37 | end 38 | end 39 | 40 | private 41 | 42 | def check!(name) 43 | raise "#{name} cannot be configured" unless @opts[name] 44 | 45 | true 46 | end 47 | 48 | def with_defaults(opts) 49 | %i[input handle].to_h do |v| 50 | [v, opts.has_key?(v) ? opts[v] : true] 51 | end 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/actions/default.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/action' 2 | require 'haveapi/actions/paginable' 3 | 4 | module HaveAPI 5 | module Actions 6 | module Default 7 | class Index < Action 8 | route '' 9 | http_method :get 10 | aliases %i[list] 11 | 12 | meta(:global) do 13 | input do 14 | bool :count, label: 'Return the count of all items', default: false 15 | end 16 | 17 | output do 18 | integer :total_count, label: 'Total count of all items' 19 | end 20 | end 21 | 22 | include HaveAPI::Actions::Paginable 23 | 24 | def pre_exec 25 | set_meta(total_count: count) if meta[:count] 26 | end 27 | 28 | # Return the total count of items. 29 | def count; end 30 | end 31 | 32 | class Create < Action 33 | route '' 34 | http_method :post 35 | aliases %i[new] 36 | end 37 | 38 | class Show < Action 39 | route ->(r) { r.singular ? '' : '{%{resource}_id}' } 40 | http_method :get 41 | aliases %i[find] 42 | end 43 | 44 | class Update < Action 45 | route ->(r) { r.singular ? '' : '{%{resource}_id}' } 46 | http_method :put 47 | end 48 | 49 | class Delete < Action 50 | route ->(r) { r.singular ? '' : '{%{resource}_id}' } 51 | http_method :delete 52 | aliases %i[destroy] 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/views/version_sidebar.erb: -------------------------------------------------------------------------------- 1 |

    Authentication

    2 |

    3 | <% if current_user %> 4 | Logged as <%= current_user.login %> [logout] 5 | <% else %> 6 | 7 | <% end %> 8 |

    9 |

    10 | <% if current_user %> 11 | Listing only accessible resources, actions and parameters. 12 | <% else %> 13 | Listing all resources, actions and parameters. 14 | <% end %> 15 |

    16 | 17 |

    Contents

    18 |
      19 |
    • API v<%= @v %>
    • 20 |
    • 21 | Authentication 22 | <%= erb :"version_sidebar/auth_nav", locals: {methods: @help[:authentication]} %> 23 |
    • 24 |
    • Resources
    • 25 | <% sort_hash(@help[:resources]).each do |resource, info| %> 26 | <%= 27 | erb :"version_sidebar/resource_nav", locals: { 28 | resource: resource, 29 | info: info, 30 | prefix: 'root', 31 | } 32 | %> 33 | <% end %> 34 |
    35 | 36 |

    Browser

    37 |

    38 | Browse this API with haveapi-webui: 39 |

    40 |

    41 | 45 | Connect 46 | 47 |

    48 | -------------------------------------------------------------------------------- /clients/go/lib/haveapi/go_client/parameters/association.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/go_client/utils' 2 | 3 | module HaveAPI::GoClient 4 | class Parameters::Association 5 | include Utils 6 | 7 | # @return [Parameter] 8 | attr_reader :parameter 9 | 10 | # @return [String] 11 | attr_reader :go_type 12 | 13 | # @return [String] 14 | attr_reader :go_value_id 15 | 16 | # @return [String] 17 | attr_reader :go_value_label 18 | 19 | # @return [Resource] 20 | attr_reader :resource 21 | 22 | def initialize(param, desc) 23 | @parameter = param 24 | @resource = find_resource(desc[:resource]) 25 | @go_type = resource.actions.detect { |a| a.name == 'show' }.output.go_type 26 | @go_value_id = camelize(desc[:value_id]) 27 | @go_value_label = camelize(desc[:value_label]) 28 | end 29 | 30 | protected 31 | 32 | def find_resource(path) 33 | root = parameter.io.action.resource.api_version 34 | path = path.clone 35 | 36 | loop do 37 | name = path.shift 38 | resource = root.resources.detect { |r| r.name == name } 39 | 40 | if resource.nil? 41 | raise "associated resource '#{name}' not found in " + 42 | (root.is_a?(ApiVersion) ? 'root' : root.resource_path.map(&:name).join('.')) 43 | 44 | elsif path.empty? 45 | return resource 46 | 47 | else 48 | root = resource 49 | end 50 | end 51 | 52 | raise 'programming error' 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /templates/ruby/basic/lib/api/resources/dummy.rb: -------------------------------------------------------------------------------- 1 | module API::Resources 2 | class Dummy < HaveAPI::Resource 3 | desc 'Dummy resource' 4 | # version '1.0' 5 | 6 | # Some data this resource will serve 7 | DUMMIES = %w[First Second Third].freeze 8 | 9 | # Create a named group of parameters 10 | params(:all) do 11 | id :id 12 | string :name 13 | end 14 | 15 | class Index < HaveAPI::Actions::Default::Index 16 | desc 'List dummies' 17 | auth false 18 | 19 | # Specify action's output 20 | output(:hash_list) do 21 | # Include a named group of parameters 22 | use :all 23 | end 24 | 25 | # Allow access to everyone 26 | authorize { allow } 27 | 28 | def exec 29 | ret = [] 30 | 31 | DUMMIES.each_with_index do |v, i| 32 | ret << { id: i, name: v } 33 | end 34 | 35 | ret 36 | end 37 | end 38 | 39 | class Show < HaveAPI::Actions::Default::Show 40 | desc 'Show a dummy' 41 | auth false 42 | 43 | # Specify action's output 44 | output(:hash) do 45 | # Include a named group of parameters 46 | use :all 47 | end 48 | 49 | # Allow access to everyone 50 | authorize { allow } 51 | 52 | def exec 53 | id = params[:dummy_id] && params[:dummy_id].to_i 54 | 55 | if DUMMIES[id].nil? 56 | error!("Dummy with id '#{id}' not found") 57 | end 58 | 59 | { id:, name: DUMMIES[id] } 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /templates/ruby/basic/README.md: -------------------------------------------------------------------------------- 1 | Basic project 2 | ============= 3 | 4 | ## Contents 5 | - `lib/` 6 | - `resources/` - contains API resources and actions, it's up to you how you 7 | structure it, I usually use one file for one resource 8 | - `dummy.rb` - an example resource 9 | - `api.rb` - method `API::default` to setup the API 10 | - `spec/` 11 | - `spec_helper.rb` - configure rspec and add helper methods 12 | - `dummy_spec.rb` - example tests for the `Dummy` resource 13 | - `config.ru` - used to run the API server 14 | - `Gemfile` - necessary dependencies 15 | - `Rakefile` - contains tasks for testing with rspec 16 | 17 | ## How to run it 18 | Enter the project directory, install dependencies using `bundle install` and 19 | run with `rackup`. 20 | 21 | ## Usage 22 | `rackup` will start a HTTP server that will serve the API. It will tell you 23 | on which port it's listening. In my case, the server listens on 24 | `http://localhost:9292`: 25 | 26 | - `GET http://localhost:9292` - list of API versions, links to documentation 27 | - `GET http://localhost:9292/v1.0/` - documentation for API version `1.0` 28 | - `OPTIONS http://localhost:9292` - JSON-formatted description of the whole API 29 | - `OPTIONS http://localhost:9292/v1.0/` - JSON-formatted description of API version `1.0` 30 | 31 | You can use any client that implements the 32 | [HaveAPI protocol](https://github.com/vpsfreecz/haveapi). 33 | 34 | ## Testing 35 | Included is support for testing with `rspec`. Notice how the `spec/spec_helper.rb` 36 | adds helper methods that you can use in your tests. 37 | -------------------------------------------------------------------------------- /examples/servers/ruby/activerecord_auth/db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # Note that this schema.rb definition is the authoritative source for your 6 | # database schema. If you need to create the application database on another 7 | # system, you should be using db:schema:load, not running all the migrations 8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 9 | # you'll amass, the slower it'll run and the greater likelihood for issues). 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(version: 1) do 14 | create_table 'auth_tokens', force: :cascade do |t| 15 | t.integer 'user_id', null: false 16 | t.string 'token', limit: 100, null: false 17 | t.datetime 'valid_to' 18 | t.string 'label' 19 | t.integer 'use_count', default: 0, null: false 20 | t.integer 'lifetime', null: false 21 | t.integer 'interval' 22 | t.datetime 'created_at' 23 | end 24 | 25 | create_table 'users', force: :cascade do |t| 26 | t.string 'username', limit: 50, null: false 27 | t.string 'password', limit: 100, null: false 28 | t.boolean 'is_admin', default: false, null: false 29 | t.datetime 'created_at' 30 | t.datetime 'updated_at' 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /servers/ruby/spec/validators/format_spec.rb: -------------------------------------------------------------------------------- 1 | describe HaveAPI::Validators::Format do 2 | shared_examples(:all) do 3 | it 'accepts a value that matches the regexp' do 4 | expect(@v.valid?('aab')).to be true 5 | expect(@v.valid?('aacacb')).to be true 6 | end 7 | 8 | it 'rejects a value that does not match the regexp' do 9 | expect(@v.valid?('aacac')).to be false 10 | expect(@v.valid?('bacacb')).to be false 11 | expect(@v.valid?('b')).to be false 12 | end 13 | end 14 | 15 | context 'with match = true' do 16 | context 'short form' do 17 | before(:each) do 18 | @v = HaveAPI::Validators::Format.new(:format, /^a[^b]+b$/) 19 | end 20 | 21 | include_examples :all 22 | end 23 | 24 | context 'full form' do 25 | before(:each) do 26 | @v = HaveAPI::Validators::Format.new(:format, { 27 | rx: /^a[^b]+b$/ 28 | }) 29 | end 30 | 31 | include_examples :all 32 | end 33 | end 34 | 35 | context 'with match = false' do 36 | before(:each) do 37 | @v = HaveAPI::Validators::Format.new(:format, { 38 | rx: /^a[^b]+b$/, 39 | match: false 40 | }) 41 | end 42 | 43 | it 'rejects a value that matches the regexp' do 44 | expect(@v.valid?('aab')).to be false 45 | expect(@v.valid?('aacacb')).to be false 46 | end 47 | 48 | it 'accepts a value if it does not match the regexp' do 49 | expect(@v.valid?('aacac')).to be true 50 | expect(@v.valid?('bacacb')).to be true 51 | expect(@v.valid?('b')).to be true 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/authentication/basic/provider.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/authentication/base' 2 | 3 | module HaveAPI::Authentication 4 | module Basic 5 | # HTTP basic authentication provider. 6 | # 7 | # Example usage: 8 | # class MyBasicAuth < HaveAPI::Authentication::Basic::Provider 9 | # protected 10 | # def find_user(request, username, password) 11 | # ::User.find_by(login: username, password: password) 12 | # end 13 | # end 14 | # 15 | # Finally put the provider in the authentication chain: 16 | # api = HaveAPI.new(...) 17 | # ... 18 | # api.auth_chain << MyBasicAuth 19 | class Provider < Base 20 | auth_method :basic 21 | 22 | def authenticate(request) 23 | user = nil 24 | 25 | auth = Rack::Auth::Basic::Request.new(request.env) 26 | if auth.provided? && auth.basic? && auth.credentials 27 | begin 28 | user = find_user(request, *auth.credentials) 29 | rescue HaveAPI::AuthenticationError 30 | user = nil 31 | end 32 | end 33 | 34 | user 35 | end 36 | 37 | def describe 38 | { 39 | description: 'Authentication using HTTP basic. Username and password is passed ' \ 40 | 'via HTTP header. Its use is forbidden from web browsers.' 41 | } 42 | end 43 | 44 | protected 45 | 46 | # Reimplement this method. It has to return an authenticated 47 | # user or nil. 48 | def find_user(request, username, password); end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /servers/elixir/test/haveapi/authentication_test.exs: -------------------------------------------------------------------------------- 1 | defmodule HaveAPI.AuthenticationTest do 2 | use ExUnit.Case 3 | use HaveAPI.Test 4 | 5 | defmodule Auth do 6 | use HaveAPI.Builder 7 | 8 | defmodule MyResource do 9 | use HaveAPI.Resource 10 | 11 | defmodule DefAuth do 12 | use HaveAPI.Action 13 | 14 | route "%{action}" 15 | 16 | def exec(_), do: :ok 17 | end 18 | 19 | defmodule NoAuth do 20 | use HaveAPI.Action 21 | route "%{action}" 22 | auth false 23 | 24 | def exec(_), do: :ok 25 | end 26 | 27 | defmodule WithAuth do 28 | use HaveAPI.Action 29 | route "%{action}" 30 | auth true 31 | 32 | def exec(_), do: :ok 33 | end 34 | 35 | actions [DefAuth, NoAuth, WithAuth] 36 | end 37 | 38 | version "1.0" do 39 | resources [MyResource] 40 | end 41 | 42 | mount "/" 43 | end 44 | 45 | test "authentication is enforced by default" do 46 | conn = call_action(Auth, "myresource", "defauth") 47 | 48 | assert conn.status == 403 49 | assert conn.resp_body["status"] === false 50 | end 51 | 52 | test "authentication enforcing can be disabled" do 53 | conn = call_action(Auth, "myresource", "noauth") 54 | 55 | assert conn.status == 200 56 | assert conn.resp_body["status"] === true 57 | end 58 | 59 | test "authentication enforcing can be explicitly enabled" do 60 | conn = call_action(Auth, "myresource", "withauth") 61 | 62 | assert conn.status == 403 63 | assert conn.resp_body["status"] === false 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /clients/go/lib/haveapi/go_client/authentication/token.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/go_client/authentication/base' 2 | 3 | module HaveAPI::GoClient 4 | class Authentication::Token < Authentication::Base 5 | register :token 6 | 7 | # HTTP header the token is sent in 8 | # @return [String] 9 | attr_reader :http_header 10 | 11 | # Query parameter the token is sent in 12 | # @return [String] 13 | attr_reader :query_parameter 14 | 15 | # Resource for token manipulation 16 | # @return [Resource] 17 | attr_reader :resource 18 | 19 | def initialize(api_version, name, desc) 20 | super 21 | @http_header = desc[:http_header] 22 | @query_parameter = desc[:query_parameter] 23 | @resource = Resource.new( 24 | api_version, 25 | :token, 26 | desc[:resources][:token], 27 | prefix: 'auth_token' 28 | ) 29 | resource.resolve_associations 30 | end 31 | 32 | def generate(gen) 33 | ErbTemplate.render_to_if_changed( 34 | 'authentication/token.go', 35 | { 36 | package: gen.package, 37 | auth: self 38 | }, 39 | File.join(gen.dst, 'auth_token.go') 40 | ) 41 | 42 | resource.generate(gen) 43 | end 44 | 45 | # @return [Action] 46 | def request_action 47 | @request_action ||= resource.actions.detect { |a| a.name == 'request' } 48 | end 49 | 50 | # @return [Array] 51 | def custom_actions 52 | @custom_actions ||= resource.actions.reject do |a| 53 | %w[request renew revoke].include?(a.name) 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /clients/js/src/haveapi/http.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class Http 3 | * @memberof HaveAPI.Client 4 | */ 5 | function Http (debug) { 6 | this.debug = debug; 7 | }; 8 | 9 | /** 10 | * @callback HaveAPI.Client.Http~replyCallback 11 | * @param {Integer} status received HTTP status code 12 | * @param {Object} response received response 13 | */ 14 | 15 | /** 16 | * @method HaveAPI.Client.Http#request 17 | */ 18 | Http.prototype.request = function(opts) { 19 | if (this.debug > 5) 20 | console.log("Request to " + opts.method + " " + opts.url); 21 | 22 | var r = new XMLHttpRequest(); 23 | 24 | if (opts.credentials === undefined) 25 | r.open(opts.method, opts.url); 26 | else 27 | r.open(opts.method, opts.url, true, opts.credentials.user, opts.credentials.password); 28 | 29 | for (var h in opts.headers) { 30 | r.setRequestHeader(h, opts.headers[h]); 31 | } 32 | 33 | if (opts.params !== undefined) 34 | r.setRequestHeader('Content-Type', 'application/json; charset=utf-8'); 35 | 36 | r.onreadystatechange = function() { 37 | var state = r.readyState; 38 | 39 | if (this.debug > 6) 40 | console.log('Request state is ' + state); 41 | 42 | if (state == 4 && opts.callback !== undefined) { 43 | var json = null; 44 | 45 | try { 46 | json = JSON.parse(r.responseText); 47 | 48 | } catch (e) { 49 | console.log('JSON.parse failed', e); 50 | } 51 | 52 | if (json) 53 | opts.callback(r.status, json); 54 | 55 | else 56 | opts.callback(false, undefined); 57 | } 58 | }; 59 | 60 | if (opts.params !== undefined) { 61 | r.send(JSON.stringify( opts.params )); 62 | 63 | } else { 64 | r.send(); 65 | } 66 | }; 67 | -------------------------------------------------------------------------------- /servers/ruby/spec/validators/length_spec.rb: -------------------------------------------------------------------------------- 1 | describe HaveAPI::Validators::Length do 2 | it 'does not allow to mix min/max with equals' do 3 | expect do 4 | HaveAPI::Validators::Length.new(:length, { min: 33, equals: 42 }) 5 | end.to raise_error(RuntimeError) 6 | end 7 | 8 | it 'requires one of min, max or equals' do 9 | expect do 10 | HaveAPI::Validators::Length.new(:length, {}) 11 | end.to raise_error(RuntimeError) 12 | end 13 | 14 | it 'checks minimum' do 15 | v = HaveAPI::Validators::Length.new(:length, { min: 2 }) 16 | expect(v.valid?('a' * 1)).to be false 17 | expect(v.valid?('a' * 2)).to be true 18 | expect(v.valid?('a' * 33)).to be true 19 | end 20 | 21 | it 'checks maximum' do 22 | v = HaveAPI::Validators::Length.new(:length, { max: 5 }) 23 | expect(v.valid?('a' * 4)).to be true 24 | expect(v.valid?('a' * 5)).to be true 25 | expect(v.valid?('a' * 6)).to be false 26 | expect(v.valid?('a' * 11)).to be false 27 | end 28 | 29 | it 'checks range' do 30 | v = HaveAPI::Validators::Length.new(:length, { min: 3, max: 6 }) 31 | expect(v.valid?('a' * 2)).to be false 32 | expect(v.valid?('a' * 3)).to be true 33 | expect(v.valid?('a' * 4)).to be true 34 | expect(v.valid?('a' * 5)).to be true 35 | expect(v.valid?('a' * 6)).to be true 36 | expect(v.valid?('a' * 7)).to be false 37 | end 38 | 39 | it 'check a specific length' do 40 | v = HaveAPI::Validators::Length.new(:length, { equals: 4 }) 41 | expect(v.valid?('a' * 2)).to be false 42 | expect(v.valid?('a' * 4)).to be true 43 | expect(v.valid?('a' * 5)).to be false 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /clients/js/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | concat = require('gulp-concat'), 3 | umd = require('gulp-umd'), 4 | jsdoc = require('gulp-jsdoc3'); 5 | 6 | var src = [ 7 | 'src/haveapi/client.js', 8 | 'src/haveapi/hooks.js', 9 | 'src/haveapi/http.js', 10 | 'src/haveapi/authentication.js', 11 | 'src/haveapi/authentication/base.js', 12 | 'src/haveapi/authentication/basic.js', 13 | 'src/haveapi/authentication/oauth2.js', 14 | 'src/haveapi/authentication/token.js', 15 | 'src/haveapi/base_resource.js', 16 | 'src/haveapi/resource.js', 17 | 'src/haveapi/action.js', 18 | 'src/haveapi/action_state.js', 19 | 'src/haveapi/response.js', 20 | 'src/haveapi/local_response.js', 21 | 'src/haveapi/resource_instance.js', 22 | 'src/haveapi/resource_instance_list.js', 23 | 'src/haveapi/parameters.js', 24 | 'src/haveapi/validator.js', 25 | 'src/haveapi/validators/*.js', 26 | 'src/haveapi/exceptions.js', 27 | 'src/*.js', 28 | ]; 29 | 30 | gulp.task('scripts', function() { 31 | return gulp.src(src) 32 | .pipe(concat('haveapi-client.js')) 33 | .pipe(umd({ 34 | exports: function (file) { 35 | return 'HaveAPI'; 36 | }, 37 | namespace: function (file) { 38 | return 'HaveAPI'; 39 | } 40 | })) 41 | .pipe(gulp.dest('./dist/')); 42 | }); 43 | 44 | gulp.task('doc', function (cb) { 45 | gulp.src(src.concat(['README.md']), {read: false}).pipe(jsdoc({ 46 | plugins: ["plugins/markdown"], 47 | opts: { destination: "html_doc" } 48 | }, cb)); 49 | }); 50 | 51 | gulp.task('watch', function() { 52 | gulp.watch('src/*.js', ['scripts', 'doc']); 53 | gulp.watch('src/**/*.js', ['scripts', 'doc']); 54 | }); 55 | 56 | gulp.task('default', gulp.series('scripts')); 57 | -------------------------------------------------------------------------------- /clients/ruby/lib/haveapi/client/action_state.rb: -------------------------------------------------------------------------------- 1 | module HaveAPI::Client 2 | # Represents action's state as returned from API resource ActionState.Show/Poll. 3 | class ActionState 4 | Progress = Struct.new(:current, :total, :unit) do 5 | def percent 6 | 100.0 / total * current 7 | end 8 | 9 | def to_s 10 | "#{current}/#{total} #{unit}" 11 | end 12 | end 13 | 14 | attr_reader :progress, :cancel_block 15 | 16 | def initialize(response) 17 | @data = response.response 18 | 19 | @progress = Progress.new(@data[:current], @data[:total], @data[:unit]) 20 | end 21 | 22 | def label 23 | @data[:label] 24 | end 25 | 26 | def status 27 | @data[:status] === true 28 | end 29 | 30 | def finished? 31 | @data[:finished] === true 32 | end 33 | 34 | def can_cancel? 35 | @data[:can_cancel] === true 36 | end 37 | 38 | # Stop monitoring the action's state and attempt to cancel it. The `block` 39 | # is given to Action.wait_for_completion for the cancel operation. The block 40 | # is used only if the cancel operation is blocking. 41 | def cancel(&block) 42 | unless can_cancel? 43 | raise "action ##{@data[:id]} (#{label}) cannot be cancelled" 44 | end 45 | 46 | @cancel = true 47 | @cancel_block = block 48 | end 49 | 50 | def cancel? 51 | @cancel === true 52 | end 53 | 54 | # Stop monitoring the action's state, the call from Action.wait_for_completion 55 | # will return. 56 | def stop 57 | @stop = true 58 | end 59 | 60 | def stop? 61 | !@stop.nil? && @stop 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /examples/servers/ruby/activerecord_auth/README.md: -------------------------------------------------------------------------------- 1 | Empty Project with ActiveRecord 2 | =============================== 3 | 4 | ## Important Files 5 | - `db/migrate/0001_setup_database.rb` - create tables for users and auth tokens 6 | - `lib/` 7 | - `authentication/{basic,token}.rb` - authentication backends 8 | - `resources/user.rb` - User resource 9 | - `api.rb` - configure authentication chain 10 | - `models/{user,auth_token}.rb` - models 11 | - `Rakefile` - task to create admin users 12 | 13 | ## Usage 14 | This API includes support for two authentication methods: `basic` and `token`. 15 | There is a model for storing users and a model for authentication tokens, see 16 | the database migration in `db/migrate/0001_setup_database.rb`. 17 | 18 | In this case, the first user has to be created using the rake task `create_admin`, 19 | e.g.: 20 | 21 | $ rake create_admin 22 | Username: admin 23 | Password: 1234 24 | 25 | Password are hashed using the bcrypt algorithm, as can be seen in 26 | `models/user.rb`. 27 | 28 | You can then authenticate to this API using the HTTP basic or token authentication 29 | methods. For example: 30 | 31 | # Start the API server, running at http://localhost:9292 32 | $ rackup 33 | 34 | # Use the CLI from haveapi-client 35 | $ haveapi-cli -u http://localhost:9292 --auth basic user list 36 | Username: admin 37 | Password: 1234 38 | 39 | Id Username Is_admin 40 | 1 admin true 41 | 42 | For the authentication methods to work, the API needs to implement interfaces 43 | from HaveAPI. These can be seen in `lib/api/authentication`. These classes are 44 | then added to HaveAPI's authentication chain in `lib/api.rb`. 45 | -------------------------------------------------------------------------------- /clients/js/src/haveapi/authentication/base.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class Base 3 | * @classdesc Base class for all authentication providers. They do not have to inherit 4 | * it directly, but must implement all necessary methods. 5 | * @memberof HaveAPI.Client.Authentication 6 | */ 7 | Authentication.Base = function (client, opts, description){}; 8 | 9 | /** 10 | * Setup the authentication provider and call the callback. 11 | * @method HaveAPI.Client.Authentication.Base#setup 12 | * @param {HaveAPI.Client~doneCallback} callback 13 | */ 14 | Authentication.Base.prototype.setup = function(callback){}; 15 | 16 | /** 17 | * Logout, destroy all resources and call the callback. 18 | * @method HaveAPI.Client.Authentication.Base#logout 19 | * @param {HaveAPI.Client~doneCallback} callback 20 | */ 21 | Authentication.Base.prototype.logout = function(callback) { 22 | callback(this.client, true); 23 | }; 24 | 25 | /** 26 | * Returns an object with keys 'user' and 'password' that are used 27 | * for HTTP basic auth. 28 | * @method HaveAPI.Client.Authentication.Base#credentials 29 | * @return {Object} credentials 30 | */ 31 | Authentication.Base.prototype.credentials = function(){}; 32 | 33 | /** 34 | * Returns an object with HTTP headers to be sent with the request. 35 | * @method HaveAPI.Client.Authentication.Base#headers 36 | * @return {Object} HTTP headers 37 | */ 38 | Authentication.Base.prototype.headers = function(){}; 39 | 40 | /** 41 | * Returns an object with query parameters to be sent with the request. 42 | * @method HaveAPI.Client.Authentication.Base#queryParameters 43 | * @return {Object} query parameters 44 | */ 45 | Authentication.Base.prototype.queryParameters = function(){}; 46 | -------------------------------------------------------------------------------- /clients/js/src/haveapi/response.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class Response 3 | * @memberof HaveAPI.Client 4 | */ 5 | function Response (action, response) { 6 | this.action = action; 7 | this.envelope = response; 8 | }; 9 | 10 | /** 11 | * Returns true if the request was successful. 12 | * @method HaveAPI.Client.Response#isOk 13 | * @return {Boolean} 14 | */ 15 | Response.prototype.isOk = function() { 16 | return this.envelope.status; 17 | }; 18 | 19 | /** 20 | * Returns the namespaced response if possible. 21 | * @method HaveAPI.Client.Response#response 22 | * @return {Object} response 23 | */ 24 | Response.prototype.response = function() { 25 | if(!this.action) 26 | return this.envelope.response; 27 | 28 | if (!this.envelope.response) 29 | return null; 30 | 31 | switch (this.action.layout('output')) { 32 | case 'object': 33 | case 'object_list': 34 | case 'hash': 35 | case 'hash_list': 36 | return this.envelope.response[ this.action.namespace('output') ]; 37 | 38 | default: 39 | return this.envelope.response; 40 | } 41 | }; 42 | 43 | /** 44 | * Return the error message received from the API. 45 | * @method HaveAPI.Client.Response#message 46 | * @return {String} 47 | */ 48 | Response.prototype.message = function() { 49 | return this.envelope.message; 50 | }; 51 | 52 | /** 53 | * Return the global meta data. 54 | * @method HaveAPI.Client.Response#meta 55 | * @return {Object} 56 | */ 57 | Response.prototype.meta = function() { 58 | var metaNs = this.action.client.apiSettings.meta.namespace; 59 | 60 | if (this.envelope.response && this.envelope.response.hasOwnProperty(metaNs)) 61 | return this.envelope.response[metaNs]; 62 | 63 | return {}; 64 | }; 65 | 66 | -------------------------------------------------------------------------------- /servers/elixir/test/haveapi/protocol_test.exs: -------------------------------------------------------------------------------- 1 | defmodule HaveAPI.ProtocolTest do 2 | use ExUnit.Case 3 | use HaveAPI.Test 4 | 5 | defmodule Api do 6 | use HaveAPI.Builder 7 | 8 | defmodule MyResource do 9 | use HaveAPI.Resource 10 | 11 | defmodule GlobalMeta do 12 | use HaveAPI.Action 13 | 14 | auth false 15 | method :post 16 | route "%{action}" 17 | 18 | meta :global do 19 | output do 20 | string :str 21 | end 22 | end 23 | 24 | def exec(_req) do 25 | {:ok, nil, %{str: "test"}} 26 | end 27 | end 28 | 29 | defmodule NoGlobalMeta do 30 | use HaveAPI.Action 31 | 32 | auth false 33 | method :post 34 | route "%{action}" 35 | 36 | meta :global do 37 | output do 38 | string :str 39 | end 40 | end 41 | 42 | def exec(_req), do: :ok 43 | end 44 | 45 | actions [ 46 | GlobalMeta, 47 | NoGlobalMeta, 48 | ] 49 | end 50 | 51 | version "1.0" do 52 | resources [MyResource] 53 | end 54 | 55 | mount "/" 56 | end 57 | 58 | test "global meta is present only if actually sent" do 59 | conn = call_action(Api, "myresource", "globalmeta") 60 | 61 | assert conn.status === 200 62 | assert conn.resp_body["status"] === true 63 | assert Map.has_key?(conn.resp_body["response"], "_meta") 64 | 65 | conn = call_action(Api, "myresource", "noglobalmeta") 66 | 67 | assert conn.status === 200 68 | assert conn.resp_body["status"] === true 69 | refute Map.has_key?(conn.resp_body["response"], "_meta") 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/authentication/base.rb: -------------------------------------------------------------------------------- 1 | module HaveAPI 2 | module Authentication 3 | # Base class for authentication providers. 4 | class Base 5 | # Get or set auth method name 6 | # @param v [Symbol, nil] 7 | # @return [Symbol] 8 | def self.auth_method(v = nil) 9 | if v 10 | @auth_method = v 11 | else 12 | @auth_method || name.split('::').last.underscore.to_sym 13 | end 14 | end 15 | 16 | def self.inherited(subclass) 17 | super 18 | subclass.send(:instance_variable_set, '@auth_method', @auth_method) 19 | end 20 | 21 | # @return [Symbol] 22 | attr_reader :name 23 | 24 | def initialize(server, v) 25 | @name = self.class.auth_method 26 | @server = server 27 | @version = v 28 | setup 29 | end 30 | 31 | # Register custom path handlers in sinatra 32 | # @param sinatra [Sinatra::Base] 33 | # @param prefix [String] 34 | def register_routes(sinatra, prefix); end 35 | 36 | # @return [Module, nil] 37 | def resource_module 38 | nil 39 | end 40 | 41 | # Reimplement this method in your authentication provider. 42 | # `request` is passed directly from Sinatra. 43 | def authenticate(request); end 44 | 45 | # Reimplement to describe provider. 46 | def describe 47 | {} 48 | end 49 | 50 | protected 51 | 52 | # Called during API mount. 53 | def setup; end 54 | 55 | # Immediately return from authentication chain. 56 | # User is not allowed to authenticate. 57 | def deny 58 | throw(:return) 59 | end 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /servers/ruby/spec/resource_spec.rb: -------------------------------------------------------------------------------- 1 | describe HaveAPI::Resource do 2 | class PluralTest < HaveAPI::Resource 3 | end 4 | 5 | class SingularTest < HaveAPI::Resource 6 | singular true 7 | end 8 | 9 | it 'has correct obj_type' do 10 | expect(PluralTest.obj_type).to eq(:resource) 11 | expect(SingularTest.obj_type).to eq(:resource) 12 | end 13 | 14 | it 'has plural name' do 15 | expect(PluralTest.resource_name).to eq('plural_tests') 16 | end 17 | 18 | it 'has singular name' do 19 | expect(SingularTest.resource_name).to eq('singular_test') 20 | end 21 | 22 | class ComplexTest < HaveAPI::Resource 23 | class Index < HaveAPI::Actions::Default::Index; end 24 | class Show < HaveAPI::Actions::Default::Show; end 25 | class Create < HaveAPI::Actions::Default::Create; end 26 | 27 | class SubResource < HaveAPI::Resource 28 | class Index < HaveAPI::Actions::Default::Index; end 29 | class Show < HaveAPI::Actions::Default::Show; end 30 | end 31 | end 32 | 33 | it 'iterates over actions' do 34 | actions = [] 35 | 36 | ComplexTest.actions do |a| 37 | actions << a 38 | end 39 | 40 | expect(actions).to contain_exactly( 41 | ComplexTest::Index, 42 | ComplexTest::Show, 43 | ComplexTest::Create 44 | ) 45 | end 46 | 47 | it 'iterates over resources' do 48 | resources = [] 49 | 50 | ComplexTest.resources do |r| 51 | resources << r 52 | end 53 | 54 | expect(resources).to contain_exactly(ComplexTest::SubResource) 55 | end 56 | 57 | it 'defines and returns resource-wide params' do 58 | ComplexTest.params(:name) { 'executed' } 59 | expect(ComplexTest.params(:name).call).to eq('executed') 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /clients/ruby/lib/haveapi/client/params.rb: -------------------------------------------------------------------------------- 1 | module HaveAPI::Client 2 | module Parameters; end 3 | 4 | class Params 5 | attr_reader :errors, :params 6 | 7 | def initialize(action, data) 8 | @action = action 9 | @data = data 10 | @params = {} 11 | @errors = {} 12 | coerce 13 | end 14 | 15 | def coerce 16 | @action.input_params.each do |name, p| 17 | next unless @data.has_key?(name) 18 | 19 | @params[name] = if p[:type] == 'Resource' 20 | Parameters::Resource.new(self, p, @data[name]) 21 | 22 | else 23 | Parameters::Typed.new(self, p, @data[name]) 24 | end 25 | end 26 | end 27 | 28 | def valid? 29 | @action.input_params.each do |name, p| 30 | next if p[:validators].nil? 31 | 32 | if p[:validators][:presence] && @params[name].nil? 33 | error(name, 'required parameter missing') 34 | 35 | elsif @params[name].nil? 36 | next 37 | end 38 | 39 | unless @params[name].valid? 40 | error(name, @params[name].errors) 41 | end 42 | end 43 | 44 | @errors.empty? 45 | end 46 | 47 | def to_api 48 | ret = {} 49 | 50 | @params.each do |name, p| 51 | ret[name] = p.to_api 52 | end 53 | 54 | ret[:meta] = @data[:meta] if @data.has_key?(:meta) 55 | 56 | ret 57 | end 58 | 59 | protected 60 | 61 | def error(param, msg) 62 | @errors[param] ||= [] 63 | 64 | if msg.is_a?(::Array) 65 | @errors[param].concat(msg) 66 | 67 | else 68 | @errors[param] << msg 69 | end 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/public/css/highlight.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Original highlight.js style (c) Ivan Sagalaev 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | background: #F0F0F0; 12 | } 13 | 14 | 15 | /* Base color: saturation 0; */ 16 | 17 | .hljs, 18 | .hljs-subst { 19 | color: #444; 20 | } 21 | 22 | .hljs-comment { 23 | color: #888888; 24 | } 25 | 26 | .hljs-keyword, 27 | .hljs-attribute, 28 | .hljs-selector-tag, 29 | .hljs-meta-keyword, 30 | .hljs-doctag, 31 | .hljs-name { 32 | font-weight: bold; 33 | } 34 | 35 | 36 | /* User color: hue: 0 */ 37 | 38 | .hljs-type, 39 | .hljs-string, 40 | .hljs-number, 41 | .hljs-selector-id, 42 | .hljs-selector-class, 43 | .hljs-quote, 44 | .hljs-template-tag, 45 | .hljs-deletion { 46 | color: #880000; 47 | } 48 | 49 | .hljs-title, 50 | .hljs-section { 51 | color: #880000; 52 | font-weight: bold; 53 | } 54 | 55 | .hljs-regexp, 56 | .hljs-symbol, 57 | .hljs-variable, 58 | .hljs-template-variable, 59 | .hljs-link, 60 | .hljs-selector-attr, 61 | .hljs-selector-pseudo { 62 | color: #BC6060; 63 | } 64 | 65 | 66 | /* Language color: hue: 90; */ 67 | 68 | .hljs-literal { 69 | color: #78A960; 70 | } 71 | 72 | .hljs-built_in, 73 | .hljs-bullet, 74 | .hljs-code, 75 | .hljs-addition { 76 | color: #397300; 77 | } 78 | 79 | 80 | /* Meta color: hue: 200 */ 81 | 82 | .hljs-meta { 83 | color: #1f7199; 84 | } 85 | 86 | .hljs-meta-string { 87 | color: #4d99bf; 88 | } 89 | 90 | 91 | /* Misc effects */ 92 | 93 | .hljs-emphasis { 94 | font-style: italic; 95 | } 96 | 97 | .hljs-strong { 98 | font-weight: bold; 99 | } 100 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/views/doc_sidebars/protocol.erb: -------------------------------------------------------------------------------- 1 |

    Contents

    2 | 43 | -------------------------------------------------------------------------------- /clients/go/README.md: -------------------------------------------------------------------------------- 1 | HaveAPI Client Generator For Go 2 | ------------------------------- 3 | HaveAPI-Go-Client generates a client library for accessing selected APIs built 4 | with the [HaveAPI framework](https://github.com/vpsfreecz/haveapi). 5 | 6 | ## Installation 7 | 8 | $ gem install 'haveapi-go-client' 9 | 10 | ## CLI 11 | The generator is run against an API server that the resulting client should be 12 | able to work with. The generated client uses one selected API version. When 13 | the API server changes, the client has to be regenerated. 14 | 15 | ``` 16 | $ haveapi-go-client -h 17 | Usage: haveapi-go-client [options] 18 | --version VERSION Use specified API version 19 | --module MODULE Name of the generated Go module 20 | --package PKG Name of the generated Go package 21 | ``` 22 | 23 | For example: 24 | 25 | ``` 26 | $ haveapi-go-client https://api.vpsfree.cz ~/go/src/foo/client 27 | ``` 28 | 29 | ## Usage 30 | The generated library can then be imported in your projects. For example, 31 | in a project with the following `go.mod`: 32 | 33 | ``` 34 | module foo 35 | ``` 36 | 37 | The client could be imported and used as: 38 | 39 | ```go 40 | package main 41 | 42 | import ( 43 | "fmt" 44 | "foo/client" 45 | ) 46 | 47 | func main() { 48 | api := client.New("https://api.vpsfree.cz") 49 | api.SetBasicAuthentication("admin", "secret") 50 | 51 | action := api.Cluster.PublicStats.Prepare() 52 | resp, err := action.Call() 53 | 54 | if err != nil { 55 | fmt.Println(err) 56 | return 57 | } 58 | 59 | fmt.Printf("%+v\n", resp) 60 | fmt.Printf("%+v\n", resp.Response) 61 | fmt.Printf("%+v\n", resp.Response.Cluster) 62 | fmt.Printf("%+v\n", resp.Output) 63 | } 64 | ``` 65 | -------------------------------------------------------------------------------- /clients/php/src/Client/Authentication/Base.php: -------------------------------------------------------------------------------- 1 | client = $client; 36 | $this->description = $description; 37 | $this->opts = $opts; 38 | 39 | $this->setup(); 40 | } 41 | 42 | /** 43 | * Called right after the constructor. 44 | * Overload it to setup your authentication provider. 45 | */ 46 | protected function setup() {} 47 | 48 | /** 49 | * Authenticate request to the API. 50 | * 51 | * Called for every request sent to the API. 52 | * @param \Httpful\Request $request 53 | */ 54 | abstract public function authenticate(Request $request); 55 | 56 | /** 57 | * Return query parameters to be sent in the request. 58 | * 59 | * Called for every request sent to the API. 60 | * @return array 61 | */ 62 | public function queryParameters() 63 | { 64 | return []; 65 | } 66 | 67 | /** 68 | * Logout, revoke all tokens, cleanup. 69 | */ 70 | public function logout() {} 71 | } 72 | -------------------------------------------------------------------------------- /clients/go/lib/haveapi/go_client/metadata.rb: -------------------------------------------------------------------------------- 1 | module HaveAPI::GoClient 2 | class Metadata 3 | class Type 4 | # @return [InputOutput, nil] 5 | attr_reader :input 6 | 7 | # @return [InputOutput, nil] 8 | attr_reader :output 9 | 10 | def initialize(action, type, desc) 11 | @input = desc[:input] && InputOutput.new( 12 | action, 13 | :"#{type}_meta", 14 | :input, 15 | desc[:input], 16 | prefix: "Meta#{type.to_s.capitalize}" 17 | ) 18 | @output = desc[:output] && InputOutput.new( 19 | action, 20 | :"#{type}_meta", 21 | :output, 22 | desc[:output], 23 | prefix: "Meta#{type.to_s.capitalize}" 24 | ) 25 | end 26 | 27 | def resolve_associations 28 | input && input.resolve_associations 29 | output && output.resolve_associations 30 | end 31 | end 32 | 33 | # @return [Type, nil] 34 | attr_reader :global 35 | 36 | # @return [Type, nil] 37 | attr_reader :object 38 | 39 | def initialize(action, desc) 40 | @global = desc[:global] && Type.new(action, :global, desc[:global]) 41 | @object = desc[:object] && Type.new(action, :object, desc[:object]) 42 | end 43 | 44 | def resolve_associations 45 | global && global.resolve_associations 46 | object && object.resolve_associations 47 | end 48 | 49 | %i[global object].each do |type| 50 | %i[input output].each do |dir| 51 | define_method(:"has_#{type}_#{dir}?") do 52 | t = send(type) 53 | next(false) unless t 54 | 55 | io = t.send(dir) 56 | next(false) unless io 57 | 58 | io.parameters.any? 59 | end 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /clients/go/lib/haveapi/go_client/parameters/base.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/go_client/utils' 2 | 3 | module HaveAPI::GoClient 4 | class Parameters::Base 5 | include Utils 6 | 7 | # Register the parameter handler 8 | # 9 | # The block is called whenever a new parameter is to be instantiated. If 10 | # this class supports the parameter, the block returns true, else false. 11 | # The first class to return true is used. 12 | # 13 | # @yieldparam role [Symbol] 14 | # @yieldparam direction [Symbol] 15 | # @yieldparam name [String] 16 | # @yieldparam desc [Hash] 17 | # @yiledreturn [Boolean, nil] 18 | def self.handle(&block) 19 | Parameter.register(self, block) 20 | end 21 | 22 | # @return [InputOutput] 23 | attr_reader :io 24 | 25 | # Parameter name in the API 26 | # @return [String] 27 | attr_reader :name 28 | 29 | # HaveAPI data type 30 | # @return [String] 31 | attr_reader :type 32 | 33 | # Parameter name in Go 34 | # @return [String] 35 | attr_reader :go_name 36 | 37 | # Go type for action input 38 | # @return [String] 39 | attr_reader :go_in_type 40 | 41 | # Go type for action output 42 | # @return [String] 43 | attr_reader :go_out_type 44 | 45 | def initialize(io, name, desc) 46 | @io = io 47 | @name = name 48 | @type = desc[:type] 49 | @desc = desc 50 | @go_name = camelize(name) 51 | end 52 | 53 | def resolve 54 | do_resolve 55 | @desc = nil 56 | end 57 | 58 | def nillable? 59 | false 60 | end 61 | 62 | def <=>(other) 63 | go_name <=> other 64 | end 65 | 66 | protected 67 | 68 | # @return [Hash] 69 | attr_reader :desc 70 | 71 | def do_resolve; end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /clients/ruby/lib/haveapi/cli/authentication/base.rb: -------------------------------------------------------------------------------- 1 | module HaveAPI::CLI 2 | module Authentication 3 | # Base class for CLI interface of authentication providers 4 | class Base 5 | class << self 6 | # Register this class as authentication provider with +name+. 7 | # The +name+ must be the same as is used in client auth provider 8 | # and on server side. 9 | # All providers have to register. 10 | def register(name) 11 | HaveAPI::CLI::Cli.register_auth_method(name, Kernel.const_get(to_s)) 12 | end 13 | end 14 | 15 | def initialize(communicator, desc, opts = {}) 16 | @communicator = communicator 17 | @desc = desc 18 | opts ||= {} 19 | 20 | opts.each do |k, v| 21 | instance_variable_set("@#{k}", v) 22 | end 23 | end 24 | 25 | # Implement this method to add CLI options for auth provider. 26 | # +opts+ is an instance of OptionParser. 27 | # This method is NOT called if the auth provider has been loaded 28 | # from the config and wasn't specified as a command line option 29 | # and therefore all necessary information must be stored in the config. 30 | def options(opts); end 31 | 32 | # Implement this method to check if all needed information 33 | # for successful authentication are provided. 34 | # Ask the user on stdin if something is missing. 35 | def validate; end 36 | 37 | # This method should call HaveAPI::Client::Communicator#authenticate 38 | # with arguments specific for this authentication provider. 39 | def authenticate; end 40 | 41 | def save 42 | @communicator.auth_save 43 | end 44 | 45 | protected 46 | 47 | attr_reader :communicator, :desc 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /servers/elixir/lib/haveapi/parameter/input.ex: -------------------------------------------------------------------------------- 1 | defmodule HaveAPI.Parameter.Input do 2 | @spec coerce(atom, any) :: any 3 | def coerce(:string, v) when is_binary(v), do: {:ok, v} 4 | 5 | def coerce(:text, v), do: coerce(:string, v) 6 | 7 | def coerce(:integer, v) when is_binary(v) do 8 | try do 9 | {:ok, String.to_integer(v)} 10 | 11 | rescue 12 | ArgumentError -> {:error, "#{inspect(v)} is not integer"} 13 | end 14 | end 15 | def coerce(:integer, v) when is_integer(v), do: {:ok, v} 16 | 17 | def coerce(:float, v) when is_binary(v) do 18 | try do 19 | {:ok, String.to_float(v)} 20 | 21 | rescue 22 | ArgumentError -> {:error, "#{inspect(v)} is not float"} 23 | end 24 | end 25 | def coerce(:float, v) when is_float(v), do: {:ok, v} 26 | 27 | def coerce(:boolean, v) when is_boolean(v), do: {:ok, v} 28 | def coerce(:boolean, "1"), do: {:ok, true} 29 | def coerce(:boolean, "0"), do: {:ok, false} 30 | 31 | def coerce(:datetime, v) when is_binary(v) do 32 | Enum.reduce_while( 33 | [:date, :datetime], 34 | nil, 35 | fn type, _acc -> 36 | case datetime(type, v) do 37 | {:ok, date} -> 38 | {:halt, {:ok, date}} 39 | 40 | # TODO: handle offset? 41 | {:ok, dt, _offset} -> 42 | {:halt, {:ok, dt}} 43 | 44 | {:error, atom} -> 45 | {:cont, {:error, "#{inspect(v)} is not in ISO 8601 format: #{atom}"}} 46 | end 47 | end 48 | ) 49 | end 50 | 51 | def coerce(:resource, v), do: {:ok, v} 52 | 53 | def coerce(:custom, v), do: {:ok, v} 54 | 55 | def coerce(type, v), do: {:error, "#{inspect(v)} is not #{type}"} 56 | 57 | defp datetime(:date, v), do: Date.from_iso8601(v) 58 | defp datetime(:datetime, v), do: DateTime.from_iso8601(v) 59 | end 60 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/validators/length.rb: -------------------------------------------------------------------------------- 1 | require 'haveapi/validator' 2 | 3 | module HaveAPI 4 | # Checks the length of a string. It does not have a short form. 5 | # 6 | # Full form: 7 | # string :param, length: { 8 | # min: 3, 9 | # max: 10 10 | # message: 'the error message' 11 | # } 12 | # 13 | # string :param, length: { 14 | # equals: 8 15 | # } 16 | class Validators::Length < Validator 17 | name :length 18 | takes :length 19 | 20 | def setup 21 | @min = take(:min) 22 | @max = take(:max) 23 | @equals = take(:equals) 24 | 25 | if (@min || @max) && @equals 26 | raise 'cannot mix min/max with equals' 27 | 28 | elsif !@min && !@max && !@equals 29 | raise 'must use either min, max or equals' 30 | end 31 | 32 | msg = if @equals 33 | "length has to be #{@equals}" 34 | 35 | elsif @min && !@max 36 | "length has to be minimally #{@min}" 37 | 38 | elsif !@min && @max 39 | "length has to be maximally #{@max}" 40 | 41 | else 42 | "length has to be in range <#{@min}, #{@max}>" 43 | end 44 | 45 | @message = take(:message, msg) 46 | end 47 | 48 | def describe 49 | ret = { 50 | message: @message 51 | } 52 | 53 | if @equals 54 | ret[:equals] = @equals 55 | 56 | else 57 | ret[:min] = @min if @min 58 | ret[:max] = @max if @max 59 | end 60 | 61 | ret 62 | end 63 | 64 | def valid?(v) 65 | len = v.length 66 | 67 | return len == @equals if @equals 68 | return len >= @min if @min && !@max 69 | return len <= @max if !@min && @max 70 | 71 | len >= @min && len <= @max 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /servers/ruby/lib/haveapi/views/version_page/auth_body.erb: -------------------------------------------------------------------------------- 1 |

    <%= name.to_s.humanize %>

    2 |
    3 | 4 |

    <%= info[:description] %>

    5 | 6 | <% if name == :token %> 7 |
    8 |
    HTTP header:
    9 |
    <%= info[:http_header] %>
    10 |
    Query parameter:
    11 |
    <%= info[:query_parameter] %>
    12 |
    13 | <% elsif name == :oauth2 %> 14 |
    15 |
    Authorize URL:
    16 |
    <%= info[:authorize_url] %>
    17 |
    Token URL:
    18 |
    <%= info[:token_url] %>
    19 |
    Revoke URL:
    20 |
    <%= info[:revoke_url] %>
    21 |
    22 | <% end %> 23 | 24 | <% if info[:resources] %> 25 |

    Resources

    26 | <% sort_hash(info[:resources]).each do |resource, desc| %> 27 | <%= erb :"version_page/resource_body", locals: {resource: resource.to_s, info: desc, path: [], prefix: 'root', name: nil} %> 28 | <% end %> 29 | <% end %> 30 | 31 | <% baseid = "auth-#{name}" %> 32 |
    33 | 34 |
    35 | <% HaveAPI::ClientExample.clients.each_with_index do |client, i| %> 36 |
    "> 37 | <%= erb :"version_page/client_auth", locals: {client: client, method: name, desc: info} %> 38 |
    39 | <% end %> 40 |
    41 | 42 | 58 | -------------------------------------------------------------------------------- /servers/elixir/test/haveapi/meta_test.exs: -------------------------------------------------------------------------------- 1 | defmodule HaveAPI.MetaTest do 2 | use ExUnit.Case 3 | use HaveAPI.Test 4 | 5 | defmodule Api do 6 | use HaveAPI.Builder 7 | 8 | defmodule MyResource do 9 | use HaveAPI.Resource 10 | 11 | defmodule MetaTemplate do 12 | use HaveAPI.Action 13 | 14 | meta :global do 15 | input do 16 | string :global_in_str 17 | end 18 | 19 | output do 20 | string :global_out_str 21 | end 22 | end 23 | 24 | meta :local do 25 | output do 26 | string :local_out_str 27 | end 28 | end 29 | end 30 | 31 | defmodule InheritedMeta do 32 | use MetaTemplate 33 | 34 | auth false 35 | method :get 36 | route "%{action}" 37 | 38 | output do 39 | string :global_in_str 40 | end 41 | 42 | def exec(req) do 43 | { 44 | :ok, 45 | %{global_in_str: req.meta.global_in_str, _meta: %{local_out_str: "local_test"}}, 46 | %{global_out_str: "global_test"} 47 | } 48 | end 49 | end 50 | 51 | actions [InheritedMeta] 52 | end 53 | 54 | version "1.0" do 55 | resources [MyResource] 56 | end 57 | 58 | mount "/" 59 | end 60 | 61 | test "meta parameters can be inherited from templates" do 62 | conn = call_api(Api, :get, "/myresource/inheritedmeta", "_meta", %{global_in_str: "test"}) 63 | 64 | assert conn.status == 200 65 | assert conn.resp_body["status"] === true 66 | assert conn.resp_body["response"]["_meta"]["global_out_str"] == "global_test" 67 | assert conn.resp_body["response"]["myresource"]["global_in_str"] == "test" 68 | assert conn.resp_body["response"]["myresource"]["_meta"]["local_out_str"] == "local_test" 69 | end 70 | end 71 | --------------------------------------------------------------------------------