├── 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 |
4 | <% @api[:versions].each do |v, info| %>
5 | <% next if v == :default %>
6 | -
7 | Version <%= v.to_s %>
8 | <%= '(default)' if v == @api[:default_version] %>
9 |
10 | <% end %>
11 |
12 |
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 | | Description: |
15 | <%= hook[:desc] %> |
16 |
17 |
18 | | Context: |
19 | <%= hook[:context] || 'current' %> |
20 |
21 |
22 | | Arguments: |
23 | <%= render_hash(hook[:args]) %> |
24 |
25 |
26 | | Initial value: |
27 | <%= render_hash(hook[:initial]) %> |
28 |
29 |
30 | | Return value: |
31 | <%= render_hash(hook[:ret]) %> |
32 |
33 |
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 | ">Login
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 |
--------------------------------------------------------------------------------