pax_global_header00006660000000000000000000000064141170622040014507gustar00rootroot0000000000000052 comment=4458fc933427000de5f615f2073bc481675158b1 diva-1.1.0/000077500000000000000000000000001411706220400124315ustar00rootroot00000000000000diva-1.1.0/.circleci/000077500000000000000000000000001411706220400142645ustar00rootroot00000000000000diva-1.1.0/.circleci/config.yml000066400000000000000000000024661411706220400162640ustar00rootroot00000000000000version: '2.1' executors: ruby: parameters: tag: type: string docker: - image: circleci/ruby:<< parameters.tag >> jobs: build: parameters: ruby-version: type: string executor: name: ruby tag: << parameters.ruby-version >> steps: - checkout - run: name: Which bundler? command: bundle -v - run: command: bundle install --path vendor/bundle - run: name: test command: bundle exec rake test lint: parameters: ruby-version: type: string executor: name: ruby tag: << parameters.ruby-version >> steps: - checkout - run: name: Which bundler? command: bundle -v - run: command: bundle install --path vendor/bundle - run: name: rubocop command: bundle exec rubocop lib/ workflows: build: jobs: - lint: name: 'lint' ruby-version: '3.0.2' - build: name: 'ruby-2.6' ruby-version: '2.6.7' requires: - 'lint' - build: name: 'ruby-2.7' ruby-version: '2.7.4' requires: - 'lint' - build: name: 'ruby-3.0' ruby-version: '3.0.2' requires: - 'lint' diva-1.1.0/.gitignore000066400000000000000000000001401411706220400144140ustar00rootroot00000000000000/.bundle/ /.yardoc /Gemfile.lock /_yardoc/ /coverage/ /doc/ /pkg/ /spec/reports/ /tmp/ /vendor/ diva-1.1.0/.rubocop.yml000066400000000000000000000140461411706220400147100ustar00rootroot00000000000000AllCops: TargetRubyVersion: 2.5 Metrics/BlockLength: Exclude: - test/**/*.rb - lib/tasks/**/*.rake - Gemfile Max: 156 Layout/LineLength: AllowHeredoc: true AllowURI: true Max: 293 Exclude: - test/**/*.rb Metrics/ModuleLength: Max: 109 CountAsOne: ['array', 'hash', 'heredoc'] Exclude: - test/**/*.rb Naming/AccessorMethodName: Enabled: false Style/Alias: Enabled: false Style/AsciiComments: Enabled: false Naming/AsciiIdentifiers: Enabled: false Metrics/BlockNesting: Enabled: false Style/CaseEquality: Enabled: false Style/ClassAndModuleChildren: Enabled: false Metrics/ClassLength: Enabled: false Style/ClassVars: Enabled: false Style/CollectionMethods: PreferredMethods: collect: map detect: find find_all: select member?: include? reduce: inject Style/ColonMethodCall: Enabled: true Style/CommentAnnotation: Enabled: false Metrics/CyclomaticComplexity: Enabled: false Style/Documentation: Enabled: false Layout/DotPosition: EnforcedStyle: leading Style/DoubleNegation: Enabled: false Style/EachWithObject: Enabled: false Style/EmptyLiteral: Enabled: true Style/Encoding: Enabled: false Style/FormatString: EnforcedStyle: percent Style/FrozenStringLiteralComment: Enabled: false Style/GlobalVars: Enabled: true Style/GuardClause: Enabled: false Style/IfUnlessModifier: Enabled: false Style/IfWithSemicolon: Enabled: false Layout/IndentationConsistency: EnforcedStyle: normal Style/InlineComment: Enabled: false Style/Lambda: EnforcedStyle: literal Style/LambdaCall: Enabled: false Metrics/MethodLength: Enabled: false Style/ModuleFunction: Enabled: false Style/NegatedIf: Enabled: true EnforcedStyle: both Style/Next: Enabled: false # false と nil の違いで分岐するようなコードは書くべきではない。 # `if a.nil?` のようなコードは、 `if a` に置き換える。 # nil と false を区別せざるを得ないところでのみ、 Style/NilComparison を無効化して nil? 利用すること Style/NilComparison: EnforcedStyle: comparison Style/NumericLiterals: Enabled: false Style/OneLineConditional: Enabled: false Style/ParallelAssignment: Enabled: false Metrics/ParameterLists: Enabled: false Naming/PredicateName: ForbiddenPrefixes: - is_ MethodDefinitionMacros: - define_method - define_singleton_method - defdsl Exclude: - 'test/*/' Style/RaiseArgs: Enabled: false Style/RegexpLiteral: Enabled: false Style/SingleLineBlockParams: Enabled: false Style/SignalException: Enabled: false Style/StringLiterals: EnforcedStyle: single_quotes Style/TrailingCommaInArrayLiteral: Enabled: true EnforcedStyleForMultiline: no_comma Style/TrailingCommaInHashLiteral: Enabled: true EnforcedStyleForMultiline: no_comma Style/TrailingCommaInArguments: Enabled: true EnforcedStyleForMultiline: no_comma Style/FormatStringToken: EnforcedStyle: template Style/TrivialAccessors: Enabled: false Lint/UnusedMethodArgument: Enabled: false Naming/VariableNumber: Enabled: false Style/WhileUntilModifier: Enabled: false Style/WordArray: Enabled: false Lint/AssignmentInCondition: AllowSafeAssignment: false Lint/SuppressedException: Enabled: false Lint/UnderscorePrefixedVariableName: AllowKeywordBlockArguments: true Lint/Void: Enabled: true Lint/UselessAssignment: Exclude: - test/**/*.rb Layout/EmptyLines: Exclude: - test/**/*.rb Style/NumericPredicate: EnforcedStyle: comparison # 結果が配列になるもの   → [] #    配列にならないもの → <> Style/PercentLiteralDelimiters: PreferredDelimiters: "%": '<>' "%i": '[]' "%q": '<>' "%Q": '<>' "%r": '<>' "%s": '<>' "%w": '[]' "%W": '[]' "%x": '<>' Layout/SpaceAroundMethodCallOperator: Enabled: true Lint/RaiseException: Enabled: true Lint/StructNewOverride: Enabled: true Style/ExponentialNotation: Enabled: true Style/HashEachMethods: Enabled: true Style/HashTransformKeys: Enabled: true Style/HashTransformValues: Enabled: true Style/PreferredHashMethods: EnforcedStyle: verbose Layout/EmptyLineAfterGuardClause: Enabled: false Style/BlockDelimiters: EnforcedStyle: braces_for_chaining Metrics/AbcSize: Max: 24.27 Style/MultilineBlockChain: Enabled: false Layout/MultilineMethodCallIndentation: EnforcedStyle: indented_relative_to_receiver Naming/MemoizedInstanceVariableName: EnforcedStyleForLeadingUnderscores: optional Layout/SpaceAroundOperators: EnforcedStyleForExponentOperator: space Layout/SpaceAroundEqualsInParameterDefault: EnforcedStyle: no_space Style/BlockComments: Enabled: false Naming/MethodName: EnforcedStyle: snake_case IgnoredPatterns: - URI - Type Naming/RescuedExceptionsVariableName: PreferredName: "exception" Layout/SpaceBeforeBrackets: # (new in 1.7) Enabled: true Lint/AmbiguousAssignment: # (new in 1.7) Enabled: true Lint/DeprecatedConstants: # (new in 1.8) Enabled: true Lint/DuplicateBranch: # (new in 1.3) Enabled: true Lint/DuplicateRegexpCharacterClassElement: # (new in 1.1) Enabled: true Lint/EmptyBlock: # (new in 1.1) Enabled: true Lint/EmptyClass: # (new in 1.3) Enabled: true Lint/LambdaWithoutLiteralBlock: # (new in 1.8) Enabled: true Lint/NoReturnInBeginEndBlocks: # (new in 1.2) Enabled: true Lint/RedundantDirGlobSort: # (new in 1.8) Enabled: true Lint/ToEnumArguments: # (new in 1.1) Enabled: true Lint/UnexpectedBlockArity: # (new in 1.5) Enabled: true Lint/UnmodifiedReduceAccumulator: # (new in 1.1) Enabled: true Style/ArgumentsForwarding: # (new in 1.1) Enabled: true Style/CollectionCompact: # (new in 1.2) Enabled: true Style/DocumentDynamicEvalDefinition: # (new in 1.1) Enabled: true Style/EndlessMethod: # (new in 1.8) Enabled: true Style/HashExcept: # (new in 1.7) Enabled: true Style/NegatedIfElseCondition: # (new in 1.2) Enabled: true Style/NilLambda: # (new in 1.3) Enabled: true Style/RedundantArgument: # (new in 1.4) Enabled: true Style/SwapValues: # (new in 1.1) Enabled: true diva-1.1.0/.travis.yml000066400000000000000000000001301411706220400145340ustar00rootroot00000000000000sudo: false language: ruby rvm: - 2.3.1 before_install: gem install bundler -v 1.12.5 diva-1.1.0/Gemfile000066400000000000000000000001311411706220400137170ustar00rootroot00000000000000source 'https://rubygems.org' # Specify your gem's dependencies in diva.gemspec gemspec diva-1.1.0/LICENSE.txt000066400000000000000000000020701411706220400142530ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2017 Toshiaki Asai Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diva-1.1.0/README.md000066400000000000000000000026421411706220400137140ustar00rootroot00000000000000# Diva This library is an implementation of expression for handling things. It replaces Retriever module of mikutter. Diva::Model is a common interface of all resources handled by mikutter. By handling data as a subclass of Diva::Model as necessary, you can obtain a common interface and it is useful for cooperation among mikutter plugins. [![toshia](https://circleci.com/gh/toshia/diva.svg?style=svg)](https://circleci.com/gh/toshia/diva) ## Installation Add this line to your application's Gemfile: ```ruby gem 'diva' ``` And then execute: $ bundle Or install it yourself as: $ gem install diva ## Usage TODO: Write usage instructions here ## Development After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). ## Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/diva. ## License The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). diva-1.1.0/Rakefile000066400000000000000000000003061411706220400140750ustar00rootroot00000000000000require "bundler/gem_tasks" require "rake/testtask" Rake::TestTask.new(:test) do |t| t.libs << "test" t.libs << "lib" t.test_files = FileList['test/**/*_test.rb'] end task :default => :test diva-1.1.0/bin/000077500000000000000000000000001411706220400132015ustar00rootroot00000000000000diva-1.1.0/bin/console000077500000000000000000000005111411706220400145660ustar00rootroot00000000000000#!/usr/bin/env ruby require "bundler/setup" require "diva" # You can add fixtures and/or initialization code here to make experimenting # with your gem easier. You can also use a different console, if you like. # (If you use this, don't forget to add pry to your Gemfile!) # require "pry" # Pry.start require "irb" IRB.start diva-1.1.0/bin/setup000077500000000000000000000002031411706220400142620ustar00rootroot00000000000000#!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' set -vx bundle install # Do any other automated setup that you need to do here diva-1.1.0/diva.gemspec000066400000000000000000000023271411706220400147250ustar00rootroot00000000000000# coding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'diva/version' Gem::Specification.new do |spec| spec.name = "diva" spec.version = Diva::VERSION spec.authors = ["Toshiaki Asai"] spec.email = ["toshi.alternative@gmail.com"] spec.summary = %q{Implementation of expression for handling things.} spec.homepage = "https://github.com/toshia/diva" spec.license = "MIT" spec.required_ruby_version = '>= 2.6.0' spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] spec.add_development_dependency "bundler", '>= 1.16.1', '< 3.0' spec.add_development_dependency "rake", '>= 13.0.1', '< 14.0' spec.add_development_dependency "minitest", '>= 5.14.4', '< 5.15' spec.add_development_dependency "irb", '>= 1.3.7', '< 2.0' spec.add_development_dependency "simplecov", '>= 0.17.1', '< 1.0' spec.add_development_dependency "rubocop", '>= 1.20.0', '< 1.21.0' spec.add_dependency "addressable", ">= 2.5.2", "< 3.0" end diva-1.1.0/lib/000077500000000000000000000000001411706220400131775ustar00rootroot00000000000000diva-1.1.0/lib/diva.rb000066400000000000000000000004351411706220400144510ustar00rootroot00000000000000# coding: utf-8 require 'diva/version' require 'diva/datasource' require 'diva/error' require 'diva/field_generator' require 'diva/field' require 'diva/model' require 'diva/spec' require 'diva/type' require 'diva/uri' module Diva def self.URI(uri) Diva::URI.new(uri) end end diva-1.1.0/lib/diva/000077500000000000000000000000001411706220400141225ustar00rootroot00000000000000diva-1.1.0/lib/diva/datasource.rb000066400000000000000000000016721411706220400166070ustar00rootroot00000000000000# -*- coding: utf-8 -*- =begin rdoc データの保存/復元を実際に担当するデータソース。 データソースをモデルにModel::add_data_retrieverにて幾つでも参加させることが出来る。 =end module Diva::DataSource USE_ALL = -1 # findbyidの引数。全てのDataSourceから探索する USE_LOCAL_ONLY = -2 # findbyidの引数。ローカルにあるデータのみを使う attr_accessor :keys # idをもつデータを返す。 # もし返せない場合は、nilを返す def findbyid(id, policy) nil end # 取得できたらそのDivaのインスタンスをキーにして実行されるDeferredを返す def idof(id) Thread.new { findbyid(id) } end alias [] idof # データの保存 # データ一件保存する。保存に成功したか否かを返す。 def store_datum(datum) false end def inspect self.class.to_s end end diva-1.1.0/lib/diva/error.rb000066400000000000000000000012311411706220400155750ustar00rootroot00000000000000# -*- coding: utf-8 -*- module Diva class DivaError < StandardError; end class InvalidTypeError < DivaError; end class InvalidEntityError < DivaError; end # 実装してもしなくてもいいメソッドが実装されておらず、結果を得られない class NotImplementedError < DivaError; end # IDやURIなどの一意にリソースを特定する情報を使ってデータソースに問い合わせたが、 # 対応する情報が見つからず、Modelを作成できない class ModelNotFoundError < DivaError; end # URIとして受け付けられない値を渡された class InvalidURIError < InvalidTypeError; end end diva-1.1.0/lib/diva/field.rb000066400000000000000000000020171411706220400155320ustar00rootroot00000000000000# -*- coding: utf-8 -*- require 'diva/type' =begin rdoc Modelのキーの情報を格納する。 キーひとつにつき1つのインスタンスが作られる。 =end module Diva class Field attr_reader :name, :type, :required # [name] Symbol フィールドの名前 # [type] Symbol フィールドのタイプ。:int, :string, :bool, :time のほか、Diva::Modelのサブクラスを指定する # [required] boolean _true_ なら、この項目を必須とする def initialize(name, type, required: false) @name = name.to_sym @type = Diva::Type.optional(Diva::Type(type)) @required = !!required end def dump_for_json(value) type.dump_for_json(value) end def required? required end def schema { name: @name.to_s, constraint: @type.schema } end def to_sym name end def to_s name.to_s end def inspect "#<#{self.class}: #{name}(#{type})#{required ? '*' : ''}>" end end end diva-1.1.0/lib/diva/field_generator.rb000066400000000000000000000015111411706220400175760ustar00rootroot00000000000000# -*- coding: utf-8 -*- class Diva::FieldGenerator def initialize(model_klass) @model_klass = model_klass end def int(field_name, required: false) @model_klass.add_field(field_name, type: :int, required: required) end def string(field_name, required: false) @model_klass.add_field(field_name, type: :string, required: required) end def bool(field_name, required: false) @model_klass.add_field(field_name, type: :bool, required: required) end def time(field_name, required: false) @model_klass.add_field(field_name, type: :time, required: required) end def uri(field_name, required: false) @model_klass.add_field(field_name, type: :uri, required: required) end def has(field_name, type, required: false) @model_klass.add_field(field_name, type: type, required: required) end end diva-1.1.0/lib/diva/model.rb000066400000000000000000000075361411706220400155620ustar00rootroot00000000000000# -*- coding: utf-8 -*- =begin rdoc いろんなリソースの基底クラス =end require 'diva/model_extend' require 'diva/uri' require 'diva/spec' require 'securerandom' class Diva::Model include Comparable extend Diva::ModelExtend def initialize(args) @value = args.dup validate self.class.store_datum(self) end # データをマージする。 # selfにあってotherにもあるカラムはotherの内容で上書きされる。 # 上書き後、データはDataSourceに保存される def merge(other) @value.update(other.to_hash) validate self.class.store_datum(self) end # このModelのパーマリンクを返す。 # パーマリンクはWebのURLで、Web上のリソースでない場合はnilを返す。 # ==== Return # 次のいずれか # [URI::HTTP|Diva::URI] パーマリンク # [nil] パーマリンクが存在しない def perma_link nil end # このModelのURIを返す。 # ==== Return # [URI::Generic|Diva::URI] パーマリンク def uri perma_link || Diva::URI.new("#{self.class.scheme}://#{self.class.host}#{path}") end # このModelが、登録されているアカウントのうちいずれかが作成したものであれば true を返す # ==== Args # [service] Service | Enumerable 「自分」のService # ==== Return # [true] 自分のによって作られたオブジェクトである # [false] 自分のによって作られたオブジェクトではない def me?(service=nil) false end def hash @_hash ||= uri.to_s.hash ^ self.class.hash end def <=>(other) if other.is_a?(Diva::Model) created - other.created elsif other.respond_to?(:[]) && other[:created] created - other[:created] else id - other end end def ==(other) if other.is_a? Diva::Model self.class == other.class && uri == other.uri end end def eql?(other) self == other end def to_hash self.class.fields.to_h { |f| [f.name, fetch(f.name)] } end def to_json(*rest) self.class.fields.to_h { |f| [f.name, f.dump_for_json(fetch(f.name))] }.to_json(*rest) end # カラムの生の内容を返す def fetch(key) @value[key.to_sym] end alias [] fetch # カラムに別の値を格納する。 # 格納後、データはDataSourceに保存される def []=(key, value) @value[key.to_sym] = value self.class.store_datum(self) end # カラムと型が違うものがある場合、例外を発生させる。 def validate raise "argument is #{@value}, not Hash" unless @value.is_a?(Hash) self.class.fields.each do |field| @value[field.name] = field.type.cast(@value[field.name]) rescue Diva::InvalidTypeError => exception raise Diva::InvalidTypeError, "#{exception} in field `#{field}'" end end # キーとして定義されていない値を全て除外した配列を生成して返す。 # また、Modelを子に含んでいる場合、それを外部キーに変換する。 def filtering datum = to_hash result = {} self.class.fields.each do |field| result[field.name] = field.type.cast(datum[field.name]) rescue Diva::InvalidTypeError => exception raise Diva::InvalidTypeError, "#{exception} in field `#{field}'" end result end # このインスタンスのタイトル。 def title fields = self.class.fields.lazy.map(&:name) if fields.include?(:name) name.gsub("\n", '') elsif fields.include?(:description) description.gsub("\n", '') else to_s.gsub("\n", '') end end def dig(key, *args) return nil unless key.respond_to?(:to_sym) value = fetch(key) if value == nil || args.empty? value else value.dig(*args) end end private # URIがデフォルトで使うpath要素 def path @path ||= "/#{SecureRandom.uuid}" end end diva-1.1.0/lib/diva/model_extend.rb000066400000000000000000000060331411706220400171200ustar00rootroot00000000000000# -*- coding: utf-8 -*- require 'diva/field' require 'diva/type' =begin rdoc Diva::Model のクラスメソッド =end module Diva::ModelExtend extend Gem::Deprecate attr_reader :slug, :spec # Modelのインスタンスのuriスキーム。オーバライドして適切な値にする # ==== Return # [String] URIスキーム def scheme @_scheme ||= to_s.split('::', 2).first.gsub(/\W/, '').downcase.freeze end # Modelのインスタンスのホスト名。オーバライドして適切な値にする # ==== Return # [String] ホスト名 def host @_host ||= to_s.split('::', 2).last.split('::').reverse.join('.').gsub(/[^\w.]/, '').downcase.freeze end # Modelにフィールドを追加する。 # ==== Args # [field_name] Symbol フィールドの名前 # [type] Symbol フィールドのタイプ。:int, :string, :bool, :time のほか、Diva::Modelのサブクラスを指定する # [required] boolean _true_ なら、この項目を必須とする def add_field(field, type: nil, required: false) if field.is_a?(Symbol) field = Diva::Field.new(field, type, required: required) end (@fields ||= []) << field define_method(field.name) do @value[field.name] end define_method("#{field.name}?") do !!@value[field.name] end define_method("#{field.name}=") do |value| @value[field.name] = field.type.cast(value) self.class.store_datum(self) value end self end def fields @fields ||= [] end alias :keys :fields deprecate :keys, 'fields', 2018, 2 def schema { fields: fields.map(&:schema), uri: uri } end def uri @uri ||= Diva::URI("diva://object.type/#{slug || SecureRandom.uuid}") end # # プライベートクラスメソッド # def field Diva::FieldGenerator.new(self) end # URIに対応するリソースの内容を持ったModelを作成する。 # URIに対応する情報はネットワーク上などから取得される場合もある。そういった場合はこのメソッドは # Delayer::Deferred::Deferredable を返す可能性がある。 # とくにオーバライドしない場合、このメソッドは常に例外 Diva::NotImplementedError を投げる。 # ==== Args # [uri] _handle_ メソッドで指定したいずれかの条件に一致するURI # ==== Return # [Delayer::Deferred::Deferredable] # ネットワークアクセスを行って取得するなど取得に時間がかかる場合 # [self] # すぐにModelを生成できる場合、そのModel # ==== Raise # [Diva::NotImplementedError] # このModelでは、find_by_uriが実装されていない # [Diva::ModelNotFoundError] # _uri_ に対応するリソースが見つからなかった def find_by_uri(uri) raise Diva::NotImplementedError, "#{self}.find_by_uri does not implement." end # Modelが生成・更新された時に呼ばれるコールバックメソッドです def store_datum(retriever); end def container_class Array end end diva-1.1.0/lib/diva/spec.rb000066400000000000000000000001541411706220400154010ustar00rootroot00000000000000# -*- coding: utf-8 -*- Diva::ModelSpec = Struct.new( :slug, :name, :reply, :myself, :timeline ) diva-1.1.0/lib/diva/type.rb000066400000000000000000000157551411706220400154450ustar00rootroot00000000000000# -*- coding: utf-8 -*- =begin rdoc Modelの各キーに格納できる値の制約。 この制約に満たない場合は、アトミックな制約であれば値の変換が行われ、そうでない場合は Diva::InvalidTypeError 例外を投げる。 これは新しくインスタンスなどを作らず、INT、FLOATなどのプリセットを利用する。 == 設定できる制約 Modelフィールドの制約には以下のようなものがある。 === アトミックな制約 以下のような値は、DivaのModelフィールドにおいてはアトミックな制約と呼び、そのまま格納できる。 [INT] 数値(Integer) [FLOAT] 少数(Float) [BOOL] 真理値(true|false) [STRING] 文字列(String) [TIME] 時刻(Time) [URI] URI(Diva::URI|URI::Generic|Addressable::URI) === Model Diva::Modelのサブクラスであれば、それを制約とすることができる。 === 配列 アトミックな制約またはModel制約を満たした値の配列を格納することができる。 配列の全ての要素が設定された制約を満たしていれば、配列制約が満たされたことになる。 =end require 'time' require 'diva/uri' module Diva::Type extend self def model_of(model) ModelType.new(model) end def array_of(type) ArrayType.new(type) end def optional(type) UnionType.new(NULL, type) end def union(*types) UnionType.new(*types) end # 全てのType共通のスーパークラス class MetaType attr_reader :name def initialize(name, *, &cast) @name = name.to_sym if cast define_singleton_method :cast, &cast end end def cast(value) value end def to_s name.to_s end def dump_for_json(value) value end def inspect "Diva::Type(#{self})" end end class AtomicType < MetaType def initialize(name, recommended_class, *rest, &cast) super(name, *rest, &cast) @recommended_classes = Array(recommended_class) end def recommendation_point(value) _, point = @recommended_classes.map.with_index { |k, i| [k, i] }.find { |k, _| value.is_a?(k) } point end def schema @schema ||= { type: uri }.freeze end def uri @uri ||= Diva::URI("diva://atomic.type/#{name}") end end INT = AtomicType.new(:int, [Integer]) do |v| case v when Integer v when Numeric, String, Time v.to_i when TrueClass 1 when FalseClass 0 else raise Diva::InvalidTypeError, "The value is not a `#{name}'." end end FLOAT = AtomicType.new(:float, [Float]) do |v| case v when Float v when Numeric, String, Time v.to_f else raise Diva::InvalidTypeError, "The value is not a `#{name}'." end end BOOL = AtomicType.new(:bool, [TrueClass, FalseClass]) do |v| case v when TrueClass, FalseClass v when String !v.empty? when Integer v != 0 else raise Diva::InvalidTypeError, "The value is not a `#{name}'." end end STRING = AtomicType.new(:string, String) do |v| case v when Diva::Model, Enumerable raise Diva::InvalidTypeError, "The value is not a `#{name}'." else v.to_s end end class TimeType < AtomicType def dump_for_json(value) cast(value).iso8601 end end TIME = TimeType.new(:time, [Time, String]) do |v| case v when Time v when Integer, Float Time.at(v) when String Time.iso8601(v) else raise Diva::InvalidTypeError, "The value is not a `#{name}'." end end URI = AtomicType.new(:uri, [Diva::URI, Addressable::URI, ::URI::Generic]) do |v| case v when Diva::URI, Addressable::URI, ::URI::Generic v when String Diva::URI.new(v) else raise Diva::InvalidTypeError, "The value is not a `#{name}'." end end NULL = AtomicType.new(:null, NilClass) do |v| if v == nil v else raise Diva::InvalidTypeError, "The value is not a `#{name}'." end end class ModelType < MetaType attr_reader :model def initialize(model, *rest, &cast) super(:model, *rest) @model = model end def recommendation_point(value) value.is_a?(model) && 0 end def cast(value) case value when model value when Hash model.new(value) else raise Diva::InvalidTypeError, "The value #{value.inspect} is not a `#{model}'." end end def schema @schema ||= { type: uri }.freeze end def to_s "#{model} #{name}" end def uri model.uri end end class ArrayType < MetaType def initialize(type) type = Diva::Type(type) super("#{type.name}_array") @type = type end def recommendation_point(values) values.is_a?(Enumerable) && values.all? { |v| @type.recommendation_point(v) } && 0 end def cast(value) raise Diva::InvalidTypeError, "The value is not a `#{name}'." unless value.is_a?(Enumerable) value.to_a.map(&@type.method(:cast)) end def dump_for_json(value) value.to_a.map(&@type.method(:dump_for_json)) end def to_s "Array of #{@type}" end def schema @schema ||= { array: @type.schema }.freeze end end class UnionType < MetaType def initialize(*types) @types = types.flatten.map(&Diva.method(:Type)).freeze super("union_#{@types.map(&:name).join('_')}") end def recommendation_point(value) @types.map { |t| t.recommendation_point(value) }.compact.min end def cast(value) recommended_type_of(value).cast(value) end def dump_for_json(value) recommended_type_of(value).dump_for_json(value) end def to_s @types.map(&:name).join('|').freeze end def schema @schema ||= { union: @types.map(&:schema) }.freeze end def recommended_type_of(value) available_types = @types.map { |t| [t, t.recommendation_point(value)] }.select { |_, p| p } unless available_types.empty? best, = available_types.min_by { |_, p| p } return best end @types.each do |type| type.cast(value) return type rescue Diva::InvalidTypeError # try next end raise Diva::InvalidTypeError, "The value is not #{self}" end end end module Diva def self.Type(type) case type when Diva::Type::MetaType type when :int Diva::Type::INT when :float Diva::Type::FLOAT when :bool Diva::Type::BOOL when :string Diva::Type::STRING when :time Diva::Type::TIME when :uri Diva::Type::URI when :null Diva::Type::NULL when ->(x) { x.instance_of?(Class) && x.ancestors.include?(Diva::Model) } Diva::Type.model_of(type) when Array if type.size >= 2 Diva::Type.union(*type) else Diva::Type.array_of(type.first) end else fail "Invalid type #{type.inspect} (#{type.class})." end end end diva-1.1.0/lib/diva/uri.rb000066400000000000000000000057451411706220400152610ustar00rootroot00000000000000# -*- coding: utf-8 -*- =begin rdoc =Model用のURIクラス mikutterでは、 URI や Addressable::URI の代わりに、このクラスを使用します。 URI や Addressable::URI に比べて、次のような特徴があります。 * コンストラクタに文字列を渡している場合、 _to_s_ がその文字列を返す。 * 正規化しないかわりに高速に動作します。 * Diva::URI のインスタンスは URI と同じように使える * unicode文字などが入っていて URI では表現できない場合、 Addressable::URI を使う * Addressable::URIでないと表現できないURIであればそちらを使うという判断を自動で行う == 使い方 Diva::URI() メソッドの引数にString, URI, Addressable::URI, Hash, Diva::URIのいずれかを与えます。 [String] uriの文字列(ex: "http://mikutter.hachune.net/") [URI] URI のインスタンス [Addressable::URI] Addressable::URI のインスタンス [Hash] これを引数にURI::Generic.build に渡すのと同じ形式の Hash [Diva::URI] 即座にこれ自身を返す == 例 Diva::URI("http://mikutter.hachune.net/") Diva::URI(URI::Generic.build(scheme: 'http', host: 'mikutter.hachune.net')) =end require 'uri' require 'addressable/uri' class Diva::URI def initialize(uri) @uri = @uri_string = @uri_hash = nil case uri.freeze when URI, Addressable::URI @uri = uri when String @uri_string = uri when Hash @uri_hash = uri end end def ==(other) case other when URI, Addressable::URI, String other.to_s == to_s when Diva::URI if has_string? || other.has_string? to_s == other.to_s else other.to_uri == to_uri end end end def hash to_s.hash ^ self.class.hash end def has_string? !!@uri_string end def has_uri? !!@uri end def to_s @uri_string ||= to_uri.to_s.freeze # rubocop:disable Naming/MemoizedInstanceVariableName end def to_uri @uri ||= generate_uri.freeze # rubocop:disable Naming/MemoizedInstanceVariableName end def scheme if has_string? && !has_uri? match = @uri_string.match(%r<\A(\w+):>) if match match[1] else to_uri.scheme end else to_uri.scheme end end def freeze unless frozen? to_uri to_s end super end def respond_to_missing?(method, include_private) to_uri.respond_to?(method, include_private) end def method_missing(method, *rest, &block) to_uri.__send__(method, *rest, &block) end private def generate_uri if @uri @uri elsif @uri_string @uri = generate_uri_by_string elsif @uri_hash @uri = generate_uri_by_hash end @uri end def generate_uri_by_string URI.parse(@uri_string) rescue URI::Error Addressable::URI.parse(@uri_string) end def generate_uri_by_hash URI::Generic.build(@uri_hash) rescue URI::Error Addressable::URI.new(@uri_hash) end end diva-1.1.0/lib/diva/version.rb000066400000000000000000000000531411706220400161320ustar00rootroot00000000000000module Diva VERSION = '1.1.0'.freeze end diva-1.1.0/test/000077500000000000000000000000001411706220400134105ustar00rootroot00000000000000diva-1.1.0/test/json_test.rb000066400000000000000000000052741411706220400157550ustar00rootroot00000000000000# -*- coding: utf-8 -*- require_relative 'test_helper' describe 'Model' do describe 'empty' do before do @mk = Class.new(Diva::Model) end it '{}が得られる' do assert_equal '{}', @mk.new({}).to_json end end describe '数値キーをもつ' do before do @mk = Class.new(Diva::Model) do field.int :id, required: true end end it '{id:1}が得られる' do assert_equal '{"id":1}', @mk.new(id: 1).to_json end end describe 'Timeキーをもつ' do before do @mk = Class.new(Diva::Model) do field.time :created_at, required: true end end it 'iso8601エンコードされた値が得られる' do assert_equal '{"created_at":"2017-12-26T12:46:45+09:00"}', @mk.new(created_at: Time.new(2017, 12, 26, 12, 46, 45, '+09:00')).to_json end end describe 'TimeのArrayキーをもつ' do before do @mk = Class.new(Diva::Model) do field.has :timestamps, [:time], required: true end end it 'iso8601エンコードされた値の配列が得られる' do assert_equal '{"timestamps":["2017-12-26T12:46:45+09:00"]}', @mk.new(timestamps: [Time.new(2017, 12, 26, 12, 46, 45, '+09:00')]).to_json end end describe 'Modelキーをもつ' do before do cmk = Class.new(Diva::Model) @mk = Class.new(Diva::Model) do field.has :child, cmk, required: true end end it '{"child":{}}が得られる' do assert_equal '{"child":{}}', @mk.new(child:{}).to_json end end describe 'Arrayキーをもつ' do before do @mk = Class.new(Diva::Model) do field.has :ids, [:int], required: true end end it '{"ids":[1,2,3]}が得られる' do assert_equal '{"ids":[1,2,3]}', @mk.new(ids: [1,2,3]).to_json end end describe 'Unionキーを持つ' do describe 'AtomicとTime' do before do @mk = Class.new(Diva::Model) do field.has :union_test, [:bool, :time], required: true end end describe 'boolを格納' do it 'boolが得られる' do assert_equal '{"union_test":true}', @mk.new(union_test: true).to_json end end describe 'iso8601文字列を格納' do it 'iso8601文字列が得られる' do assert_equal '{"union_test":"2017-12-26T12:46:45+09:00"}', @mk.new(union_test: "2017-12-26T12:46:45+09:00").to_json end end describe 'Timeを格納' do it 'iso8601文字列が得られる' do assert_equal '{"union_test":"2017-12-26T12:46:45+09:00"}', @mk.new(union_test: Time.new(2017, 12, 26, 12, 46, 45, '+09:00')).to_json end end end end end diva-1.1.0/test/model_test.rb000066400000000000000000000212261411706220400160770ustar00rootroot00000000000000# -*- coding: utf-8 -*- require_relative 'test_helper' describe 'Model' do before do @mk = Class.new(Diva::Model) end describe 'Field' do describe 'atomic type' do before do @mk.add_field(:field_0, type: :int) @mi = @mk.new(field_0: 1) end it 'Accessorの定義' do assert_respond_to @mi, :field_0, 'Reader does not defined.' assert_respond_to @mi, :field_0?, 'Reader does not defined.' assert_respond_to @mi, :field_0=, 'Writer does not defined.' end it '値を読み取る' do assert_equal 1, @mi.field_0 end it '値の存在確認をする' do assert @mi.field_0?, 'field_0は真値なので#field_0?はtrueを返す' end describe '値を更新する' do it '39 へ更新すると、そのまま39が格納される' do @mi.field_0 = 39 assert_equal 39, @mi.field_0 end it '39.25 へ更新すると、to_iされた値が格納される' do @mi.field_0 = 39.25 assert_equal 39.25.to_i, @mi.field_0 end it '"39" へ更新すると、to_iされた値が格納される' do @mi.field_0 = "39" assert_equal "39".to_i, @mi.field_0 end it '"abc" へ更新すると、to_iされた値が格納される' do @mi.field_0 = "abc" assert_equal "abc".to_i, @mi.field_0 end it 'Time へ更新すると、 Diva::InvalidTypeError 例外を投げる' do time = Time.new(2009, 12, 25) @mi.field_0 = time assert_equal time.to_i, @mi.field_0 end it 'Model へ更新しようとすると、 Diva::InvalidTypeError 例外を投げる' do assert_raises(Diva::InvalidTypeError) do @mi.field_0 = @mk.new(field_0: 42) end end it '[39, 42] (intの配列) へ更新しようとすると、 Diva::InvalidTypeError 例外を投げる' do assert_raises(Diva::InvalidTypeError) do @mi.field_0 = [39, 42] end end end it 'inspect' do assert_instance_of String, @mk.fields.inspect end end describe 'model type (subclass)' do before do @child_k = Class.new(Diva::Model) @child = @child_k.new({}) @mk.add_field(:child, type: @child_k) @mi = @mk.new(child: @child) end it 'Accessorの定義' do assert_respond_to @mi, :child, 'Reader does not defined.' assert_respond_to @mi, :child?, 'Reader does not defined.' assert_respond_to @mi, :child=, 'Writer does not defined.' end it '値を読み取る' do assert_equal @child, @mi.child end it '値の存在確認をする' do assert @mi.child?, 'childは真値なので#child?はtrueを返す' end describe '値を更新する' do it '39 へ更新すると、 Diva::InvalidTypeError 例外を投げる' do assert_raises(Diva::InvalidTypeError) do @mi.child = 39 end end it '39.25 へ更新すると、 Diva::InvalidTypeError 例外を投げる' do assert_raises(Diva::InvalidTypeError) do @mi.child = 39.25 end end it '"39" へ更新すると、 Diva::InvalidTypeError 例外を投げる' do assert_raises(Diva::InvalidTypeError) do @mi.child = "39" end end it '"abc" へ更新すると、 Diva::InvalidTypeError 例外を投げる' do assert_raises(Diva::InvalidTypeError) do @mi.child = "abc" end end it 'Time へ更新すると、 Diva::InvalidTypeError 例外を投げる' do assert_raises(Diva::InvalidTypeError) do time = Time.new(2009, 12, 25) @mi.child = time end end it 'Model へ更新しようとすると、そのインスタンスがそのまま格納される' do newval = @child_k.new({}) mi2 = @mk.new(child: newval) assert_equal newval, mi2.child end it '[39, 42] (intの配列) へ更新しようとすると、 Diva::InvalidTypeError 例外を投げる' do assert_raises(Diva::InvalidTypeError) do @mi.child = [39, 42] end end it 'Modelの配列へ更新しようとすると、 Diva::InvalidTypeError 例外を投げる' do mc = @mk.new(child: nil) assert_raises(Diva::InvalidTypeError) do @mi.child = [mc] end end end end describe 'model type' do before do @child_k = Class.new(Diva::Model) @child = @child_k.new({}) @mk.add_field(:child, type: Diva::Model) @mi = @mk.new(child: @child) end it 'Accessorの定義' do assert_respond_to @mi, :child, 'Reader does not defined.' assert_respond_to @mi, :child?, 'Reader does not defined.' assert_respond_to @mi, :child=, 'Writer does not defined.' end it '値を読み取る' do assert_equal @child, @mi.child end it '値の存在確認をする' do assert @mi.child?, 'childは真値なので#child?はtrueを返す' end describe '値を更新する' do it '39 へ更新すると、 Diva::InvalidTypeError 例外を投げる' do assert_raises(Diva::InvalidTypeError) do @mi.child = 39 end end it '39.25 へ更新すると、 Diva::InvalidTypeError 例外を投げる' do assert_raises(Diva::InvalidTypeError) do @mi.child = 39.25 end end it '"39" へ更新すると、 Diva::InvalidTypeError 例外を投げる' do assert_raises(Diva::InvalidTypeError) do @mi.child = "39" end end it '"abc" へ更新すると、 Diva::InvalidTypeError 例外を投げる' do assert_raises(Diva::InvalidTypeError) do @mi.child = "abc" end end it 'Time へ更新すると、 Diva::InvalidTypeError 例外を投げる' do assert_raises(Diva::InvalidTypeError) do time = Time.new(2009, 12, 25) @mi.child = time end end it 'Model へ更新しようとすると、そのインスタンスがそのまま格納される' do newval = @child_k.new({}) mi2 = @mk.new(child: newval) assert_equal newval, mi2.child end it '[39, 42] (intの配列) へ更新しようとすると、 Diva::InvalidTypeError 例外を投げる' do assert_raises(Diva::InvalidTypeError) do @mi.child = [39, 42] end end it 'Modelの配列へ更新しようとすると、 Diva::InvalidTypeError 例外を投げる' do mc = @mk.new(child: nil) assert_raises(Diva::InvalidTypeError) do @mi.child = [mc] end end end end end describe 'URI' do it 'とくに定義のないModelでもURIを取得できる' do mi = @mk.new({}) assert_instance_of Diva::URI, mi.uri end end describe 'dig' do before do @mk.add_field(:field_0, type: :string) end it '存在しないフィールドを取得' do @mi = @mk.new({}) assert_nil @mi.dig('foobar') end it 'Symbolに変換できないフィールドを取得' do refute 0.respond_to?(:to_sym), 'Integer#to_symが実装されているためテストにならない' @mi = @mk.new({}) assert_nil @mi.dig(0) end it '存在するフィールドを取得' do value = SecureRandom.uuid @mi = @mk.new(field_0: value) assert_equal value, @mi.dig('field_0') end describe 'Arrayを含むキー' do before do @mk.add_field(:field_1, type: [:string]) @mi = @mk.new(field_1: ['a', 'b', 'c']) end it '存在しない要素を取得' do assert_nil @mi.dig('field_1', 100) end it '存在する要素を取得' do assert_equal 'a', @mi.dig('field_1', 0) assert_equal 'b', @mi.dig('field_1', 1) assert_equal 'c', @mi.dig('field_1', 2) end end describe 'Modelを含むArray' do before do @mi_list = [@mk.new(field_0: 'a'), @mk.new(field_0: 'b'), @mk.new(field_0: 'c')] end it '存在しない要素を取得' do assert_nil @mi_list.dig(0, 'foobar') end it '存在する要素を取得' do assert_equal 'a', @mi_list.dig(0, 'field_0') assert_equal 'b', @mi_list.dig(1, 'field_0') assert_equal 'c', @mi_list.dig(2, 'field_0') end end end end diva-1.1.0/test/test_helper.rb000066400000000000000000000002131411706220400162470ustar00rootroot00000000000000$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) require 'simplecov' SimpleCov.start require 'diva' require 'minitest/autorun' diva-1.1.0/test/type_test.rb000066400000000000000000000563001411706220400157610ustar00rootroot00000000000000# -*- coding: utf-8 -*- require_relative 'test_helper' describe 'Type' do # # INT # describe 'INT' do it 'nameが"int"' do assert_equal :int, Diva::Type::INT.name end describe 'キャスト' do it 'intから' do assert_equal 39, Diva::Type::INT.cast(39) end it 'floatから' do assert_equal 39.25.to_i, Diva::Type::INT.cast(39.25) end describe 'boolから' do it 'true' do assert_equal 1, Diva::Type::INT.cast(true) end it 'false' do assert_equal 0, Diva::Type::INT.cast(false) end end describe 'stringから' do it '"39"を' do assert_equal 39, Diva::Type::INT.cast("39") end it '"abc"を' do assert_equal 0, Diva::Type::INT.cast("abc") end end it 'Timeから' do time = Time.new(2009, 12, 25) assert_equal time.to_i, Diva::Type::INT.cast(time) end it 'URI::Genericから' do uri = URI.parse('http://mikutter.hachune.net/') assert_raises(Diva::InvalidTypeError) do Diva::Type::INT.cast(uri) end end it 'Diva::URIから' do uri = Diva::URI.new('http://mikutter.hachune.net/') assert_raises(Diva::InvalidTypeError) do Diva::Type::INT.cast(uri) end end it 'Addressable::URIから' do uri = Addressable::URI.parse('http://mikutter.hachune.net/') assert_raises(Diva::InvalidTypeError) do Diva::Type::INT.cast(uri) end end it 'Modelから' do mk = Class.new(Diva::Model) mi = mk.new({}) assert_raises(Diva::InvalidTypeError) do Diva::Type::INT.cast(mi) end end it '配列から' do assert_raises(Diva::InvalidTypeError) do Diva::Type::INT.cast(['156']) end end end end # # FLOAT # describe 'FLOAT' do it 'nameが"float"' do assert_equal :float, Diva::Type::FLOAT.name end describe 'キャスト' do it 'intから' do assert_equal 39, Diva::Type::FLOAT.cast(39) assert_kind_of Float, Diva::Type::FLOAT.cast(39) end it 'floatから' do assert_equal 39.25, Diva::Type::FLOAT.cast(39.25) end describe 'boolから' do it 'true' do assert_raises(Diva::InvalidTypeError) do Diva::Type::FLOAT.cast(true) end end it 'false' do assert_raises(Diva::InvalidTypeError) do Diva::Type::FLOAT.cast(false) end end end describe 'stringから' do it '"39"を' do assert_equal 39.0, Diva::Type::FLOAT.cast("39") end it '"abc"を' do assert_equal 0.0, Diva::Type::FLOAT.cast("abc") end end it 'Timeから' do time = Time.new(2009, 12, 25) assert_equal time.to_f, Diva::Type::FLOAT.cast(time) end it 'URI::Genericから' do uri = URI.parse('http://mikutter.hachune.net/') assert_raises(Diva::InvalidTypeError) do Diva::Type::FLOAT.cast(uri) end end it 'Diva::URIから' do uri = Diva::URI.new('http://mikutter.hachune.net/') assert_raises(Diva::InvalidTypeError) do Diva::Type::FLOAT.cast(uri) end end it 'Addressable::URIから' do uri = Addressable::URI.parse('http://mikutter.hachune.net/') assert_raises(Diva::InvalidTypeError) do Diva::Type::FLOAT.cast(uri) end end it 'Modelから' do mk = Class.new(Diva::Model) mi = mk.new({}) assert_raises(Diva::InvalidTypeError) do Diva::Type::FLOAT.cast(mi) end end it '配列から' do assert_raises(Diva::InvalidTypeError) do Diva::Type::FLOAT.cast(['156']) end end end end # # BOOL # describe 'BOOL' do it 'nameが"bool"' do assert_equal :bool, Diva::Type::BOOL.name end describe 'キャスト' do describe 'intから' do it '0' do assert_equal false, Diva::Type::BOOL.cast(0) end it '非0' do assert_equal true, Diva::Type::BOOL.cast(1) end end it 'floatから' do assert_raises(Diva::InvalidTypeError) do Diva::Type::BOOL.cast(39.25) end end describe 'boolから' do it 'true' do assert_equal true, Diva::Type::BOOL.cast(true) end it 'false' do assert_equal false, Diva::Type::BOOL.cast(false) end end describe 'stringから' do it '"39"を' do assert_equal true, Diva::Type::BOOL.cast("39") end it '"abc"を' do assert_equal true, Diva::Type::BOOL.cast("abc") end it '"false"を' do assert_equal true, Diva::Type::BOOL.cast("false") end it '"nil"を' do assert_equal true, Diva::Type::BOOL.cast("nil") end it '"null"を' do assert_equal true, Diva::Type::BOOL.cast("null") end it '"0"を' do assert_equal true, Diva::Type::BOOL.cast("0") end it '""を' do assert_equal false, Diva::Type::BOOL.cast("") end end it 'Timeから' do time = Time.new(2009, 12, 25) assert_raises(Diva::InvalidTypeError) do Diva::Type::BOOL.cast(time) end end it 'URI::Genericから' do uri = URI.parse('http://mikutter.hachune.net/') assert_raises(Diva::InvalidTypeError) do Diva::Type::BOOL.cast(uri) end end it 'Diva::URIから' do uri = Diva::URI.new('http://mikutter.hachune.net/') assert_raises(Diva::InvalidTypeError) do Diva::Type::BOOL.cast(uri) end end it 'Addressable::URIから' do uri = Addressable::URI.parse('http://mikutter.hachune.net/') assert_raises(Diva::InvalidTypeError) do Diva::Type::BOOL.cast(uri) end end it 'Modelから' do mk = Class.new(Diva::Model) mi = mk.new({}) assert_raises(Diva::InvalidTypeError) do Diva::Type::BOOL.cast(mi) end end it '配列から' do assert_raises(Diva::InvalidTypeError) do Diva::Type::BOOL.cast(['156']) end end end end # # STRING # describe 'STRING' do it 'nameが"string"' do assert_equal :string, Diva::Type::STRING.name end describe 'キャスト' do it 'intから' do assert_equal "39", Diva::Type::STRING.cast(39) end it 'floatから' do assert_equal "39.25", Diva::Type::STRING.cast(39.25) end describe 'boolから' do it 'true' do assert_equal 'true', Diva::Type::STRING.cast(true) end it 'false' do assert_equal 'false', Diva::Type::STRING.cast(false) end end describe 'stringから' do it '"39"を' do assert_equal '39', Diva::Type::STRING.cast("39") end it '"abc"を' do assert_equal 'abc', Diva::Type::STRING.cast("abc") end end it 'Timeから' do time = Time.new(2009, 12, 25) assert_equal time.to_s, Diva::Type::STRING.cast(time) end it 'URI::Genericから' do expect = 'http://mikutter.hachune.net/' uri = URI.parse(expect) assert_equal expect, Diva::Type::STRING.cast(uri) end it 'Diva::URIから' do expect = 'http://mikutter.hachune.net/' uri = Diva::URI.new(expect) assert_equal expect, Diva::Type::STRING.cast(uri) end it 'Addressable::URIから' do expect = 'http://mikutter.hachune.net/' uri = Addressable::URI.parse(expect) assert_equal expect, Diva::Type::STRING.cast(uri) end it 'Modelから' do mk = Class.new(Diva::Model) mi = mk.new({}) assert_raises(Diva::InvalidTypeError) do Diva::Type::STRING.cast(mi) end end it '配列から' do assert_raises(Diva::InvalidTypeError) do Diva::Type::STRING.cast(['156']) end end end end # # TIME # describe 'TIME' do it 'nameが"time"' do assert_equal :time, Diva::Type::TIME.name end describe 'キャスト' do it 'intから' do assert_equal Time.at(39), Diva::Type::TIME.cast(39) end it 'floatから' do assert_equal Time.at(39.25), Diva::Type::TIME.cast(39.25) end describe 'boolから' do it 'true' do assert_raises(Diva::InvalidTypeError) do Diva::Type::TIME.cast(true) end end it 'false' do assert_raises(Diva::InvalidTypeError) do Diva::Type::TIME.cast(false) end end end describe 'stringから' do it '"39"を' do assert_raises(ArgumentError){ Diva::Type::TIME.cast("39") } end it '"abc"を' do assert_raises(ArgumentError){ Diva::Type::TIME.cast("abc") } end it '"2017-12-26T12:46:45+09:00"を' do assert_equal Time.new(2017, 12, 26, 12, 46, 45, '+09:00'), Diva::Type::TIME.cast("2017-12-26T12:46:45+09:00") end end it 'Timeから' do time = Time.new(2009, 12, 25) assert_equal time, Diva::Type::TIME.cast(time) end it 'URI::Genericから' do uri = URI.parse('http://mikutter.hachune.net/') assert_raises(Diva::InvalidTypeError) do Diva::Type::TIME.cast(uri) end end it 'Diva::URIから' do uri = Diva::URI.new('http://mikutter.hachune.net/') assert_raises(Diva::InvalidTypeError) do Diva::Type::TIME.cast(uri) end end it 'Addressable::URIから' do uri = Addressable::URI.parse('http://mikutter.hachune.net/') assert_raises(Diva::InvalidTypeError) do Diva::Type::TIME.cast(uri) end end it 'Modelから' do mk = Class.new(Diva::Model) mi = mk.new({}) assert_raises(Diva::InvalidTypeError) do Diva::Type::TIME.cast(mi) end end it '配列から' do assert_raises(Diva::InvalidTypeError) do Diva::Type::TIME.cast(['156']) end end end describe 'JSONダンプ' do it 'ダンプする' do time = Time.new(2017, 12, 26, 12, 46, 45, '+09:00') assert_equal time.iso8601, Diva::Type::TIME.dump_for_json(time) end end end # # URI # describe 'URI' do it 'nameが"uri"' do assert_equal :uri, Diva::Type::URI.name end describe 'キャスト' do it 'intから' do assert_raises(Diva::InvalidTypeError) do Diva::Type::URI.cast(39) end end it 'floatから' do assert_raises(Diva::InvalidTypeError) do Diva::Type::URI.cast(39.25) end end describe 'boolから' do it 'true' do assert_raises(Diva::InvalidTypeError) do Diva::Type::URI.cast(true) end end it 'false' do assert_raises(Diva::InvalidTypeError) do Diva::Type::URI.cast(false) end end end describe 'stringから' do it '"39"を' do uri = Diva::Type::URI.cast("39") assert_equal '39', uri.to_s assert_kind_of Diva::URI, uri assert_nil uri.scheme end it '"abc"を' do uri = Diva::Type::URI.cast("abc") assert_equal 'abc', uri.to_s assert_kind_of Diva::URI, uri assert_nil uri.scheme end it '"http://mikutter.hachune.net/"を' do uri = Diva::Type::URI.cast("http://mikutter.hachune.net/") assert_equal 'http://mikutter.hachune.net/', uri.to_s assert_kind_of Diva::URI, uri assert_equal 'http', uri.scheme end end it 'Timeから' do time = Time.new(2009, 12, 25) assert_raises(Diva::InvalidTypeError) do Diva::Type::URI.cast(time) end end it 'URI::Genericから' do uri = URI.parse('http://mikutter.hachune.net/') assert_equal uri, Diva::Type::URI.cast(uri) end it 'Diva::URIから' do uri = Diva::URI.new('http://mikutter.hachune.net/') assert_equal uri, Diva::Type::URI.cast(uri) end it 'Addressable::URIから' do uri = Addressable::URI.parse('http://mikutter.hachune.net/') assert_equal uri, Diva::Type::URI.cast(uri) end it 'Modelから' do mk = Class.new(Diva::Model) mi = mk.new({}) assert_raises(Diva::InvalidTypeError) do Diva::Type::URI.cast(mi) end end it '配列から' do assert_raises(Diva::InvalidTypeError) do Diva::Type::URI.cast(['156']) end end end end # # Model # describe 'Model' do before do @mk = Class.new(Diva::Model) do field.string :description field.string :name, required: true field.int :id, required: true end @constraint = Diva::Type.model_of(@mk) end it 'nameが"model"' do assert_equal :model, @constraint.name end describe 'キャスト' do it 'intから' do assert_raises(Diva::InvalidTypeError) do @constraint.cast(39) end end it 'floatから' do assert_raises(Diva::InvalidTypeError) do @constraint.cast(39.25) end end describe 'boolから' do it 'true' do assert_raises(Diva::InvalidTypeError) do @constraint.cast(true) end end it 'false' do assert_raises(Diva::InvalidTypeError) do @constraint.cast(false) end end end describe 'stringから' do it '"39"を' do assert_raises(Diva::InvalidTypeError) do @constraint.cast("39") end end it '"abc"を' do assert_raises(Diva::InvalidTypeError) do @constraint.cast("abc") end end end it 'Timeから' do time = Time.new(2009, 12, 25) assert_raises(Diva::InvalidTypeError) do @constraint.cast(time) end end it 'URI::Genericから' do uri = URI.parse('http://mikutter.hachune.net/') assert_raises(Diva::InvalidTypeError) do @constraint.cast(uri) end end it 'Diva::URIから' do uri = Diva::URI.new('http://mikutter.hachune.net/') assert_raises(Diva::InvalidTypeError) do @constraint.cast(uri) end end it 'Addressable::URIから' do uri = Addressable::URI.parse('http://mikutter.hachune.net/') assert_raises(Diva::InvalidTypeError) do @constraint.cast(uri) end end describe 'Modelから' do it '正しいModel' do mi = @mk.new({name: '名前', id: 1}) assert_equal mi, @constraint.cast(mi) end it 'Modelのサブクラス' do mi = Class.new(Diva::Model).new({}) assert_raises(Diva::InvalidTypeError) do @constraint.cast(mi) end end end it '配列から' do assert_raises(Diva::InvalidTypeError) do @constraint.cast(['156']) end end it 'Hashから' do result = @constraint.cast({name: "名前", id: '42', description: '詳細'}) assert_instance_of @mk, result assert_equal '詳細', result.description assert_equal '名前', result.name assert_equal 42, result.id end end end # # Modelのサブクラス # describe 'Modelのサブクラス' do before do @mk = Class.new(Diva::Model) @constraint = Diva::Type.model_of(@mk) end it 'nameが"model"' do assert_equal :model, @constraint.name end describe 'キャスト' do it 'intから' do assert_raises(Diva::InvalidTypeError) do @constraint.cast(39) end end it 'floatから' do assert_raises(Diva::InvalidTypeError) do @constraint.cast(39.25) end end describe 'boolから' do it 'true' do assert_raises(Diva::InvalidTypeError) do @constraint.cast(true) end end it 'false' do assert_raises(Diva::InvalidTypeError) do @constraint.cast(false) end end end describe 'stringから' do it '"39"を' do assert_raises(Diva::InvalidTypeError) do @constraint.cast("39") end end it '"abc"を' do assert_raises(Diva::InvalidTypeError) do @constraint.cast("abc") end end end it 'Timeから' do time = Time.new(2009, 12, 25) assert_raises(Diva::InvalidTypeError) do @constraint.cast(time) end end it 'URI::Genericから' do uri = URI.parse('http://mikutter.hachune.net/') assert_raises(Diva::InvalidTypeError) do @constraint.cast(uri) end end it 'Diva::URIから' do uri = Diva::URI.new('http://mikutter.hachune.net/') assert_raises(Diva::InvalidTypeError) do @constraint.cast(uri) end end it 'Addressable::URIから' do uri = Addressable::URI.parse('http://mikutter.hachune.net/') assert_raises(Diva::InvalidTypeError) do @constraint.cast(uri) end end describe 'Modelから' do it '正しいModel' do mi = @mk.new({}) assert_equal mi, @constraint.cast(mi) end it '想定していないModel' do mi = Class.new(Diva::Model).new({}) assert_raises(Diva::InvalidTypeError) do @constraint.cast(mi) end end end it '配列から' do assert_raises(Diva::InvalidTypeError) do @constraint.cast(['156']) end end end end describe 'Array' do describe 'Stringの' do before do @constraint = Diva::Type.array_of(Diva::Type::STRING) end it 'nameが"string_array"' do assert_equal :string_array, @constraint.name end describe 'キャスト' do it 'intから' do assert_raises(Diva::InvalidTypeError) do @constraint.cast(39) end end it 'floatから' do assert_raises(Diva::InvalidTypeError) do @constraint.cast(39.25) end end describe 'boolから' do it 'true' do assert_raises(Diva::InvalidTypeError) do @constraint.cast(true) end end it 'false' do assert_raises(Diva::InvalidTypeError) do @constraint.cast(false) end end end describe 'stringから' do it '"39"を' do assert_raises(Diva::InvalidTypeError) do @constraint.cast("39") end end it '"abc"を' do assert_raises(Diva::InvalidTypeError) do @constraint.cast("abc") end end end it 'Timeから' do time = Time.new(2009, 12, 25) assert_raises(Diva::InvalidTypeError) do @constraint.cast(time) end end it 'URI::Genericから' do uri = URI.parse('http://mikutter.hachune.net/') assert_raises(Diva::InvalidTypeError) do @constraint.cast(uri) end end it 'Diva::URIから' do uri = Diva::URI.new('http://mikutter.hachune.net/') assert_raises(Diva::InvalidTypeError) do @constraint.cast(uri) end end it 'Addressable::URIから' do uri = Addressable::URI.parse('http://mikutter.hachune.net/') assert_raises(Diva::InvalidTypeError) do @constraint.cast(uri) end end it 'Modelから' do mk = Class.new(Diva::Model) mi = mk.new({}) assert_raises(Diva::InvalidTypeError) do @constraint.cast(mi) end end describe '配列から' do it 'stringの配列' do expect = ['foo', 'bar'] assert_equal expect, @constraint.cast(expect) end it '空の配列' do expect = [] assert_equal expect, @constraint.cast(expect) end it 'intの配列' do expect = [1, 2] assert_equal ['1', '2'], @constraint.cast(expect) end it 'Modelの配列' do mk = Class.new(Diva::Model) expect = [mk.new({}), mk.new({})] assert_raises(Diva::InvalidTypeError) do @constraint.cast(expect) end end end end end describe 'Modelの' do before do @mc = Class.new(Diva::Model) @constraint = Diva::Type.array_of(@mc) end describe '配列から' do it 'Modelの配列' do expect = [@mc.new({})] assert_equal expect, @constraint.cast(expect) end end end describe 'Union' do before do @constraint = Diva::Type.union(Diva::Type::STRING, Diva::Type::INT) end describe 'キャスト' do it 'intから' do assert_equal 39, @constraint.cast(39) end it 'floatから' do assert_equal "39.25", @constraint.cast(39.25) end describe 'boolから' do it 'true' do assert_equal 'true', @constraint.cast(true) end it 'false' do assert_equal 'false', @constraint.cast(false) end end describe 'stringから' do it '"39"を' do assert_equal '39', @constraint.cast("39") end it '"abc"を' do assert_equal 'abc', @constraint.cast("abc") end end it 'Timeから' do time = Time.new(2009, 12, 25) assert_equal time.to_s, @constraint.cast(time) end it 'URI::Genericから' do expect = 'http://mikutter.hachune.net/' uri = URI.parse(expect) assert_equal expect, @constraint.cast(uri) end it 'Diva::URIから' do expect = 'http://mikutter.hachune.net/' uri = Diva::URI.new(expect) assert_equal expect, @constraint.cast(uri) end it 'Addressable::URIから' do expect = 'http://mikutter.hachune.net/' uri = Addressable::URI.parse(expect) assert_equal expect, @constraint.cast(uri) end it 'Modelから' do mk = Class.new(Diva::Model) mi = mk.new({}) assert_raises(Diva::InvalidTypeError) do @constraint.cast(mi) end end it '配列から' do assert_raises(Diva::InvalidTypeError) do @constraint.cast(['156']) end end end end end end diva-1.1.0/test/uri_test.rb000066400000000000000000000036641411706220400156040ustar00rootroot00000000000000# -*- coding: utf-8 -*- require_relative 'test_helper' describe 'URI' do describe 'httpスキーム' do before do @uri = Diva::URI('http://mikutter.hachune.net/') end it '文字列表現を得ることができる' do assert_equal 'http://mikutter.hachune.net/', @uri.to_s end describe '情報を得る' do it 'scheme' do assert_equal 'http', @uri.scheme end it 'host' do assert_equal 'mikutter.hachune.net', @uri.host end it 'path' do assert_equal '/', @uri.path end end describe '比較' do it '一致するString' do assert @uri == 'http://mikutter.hachune.net/' end it 'URIではないString' do refute @uri == 'テストテキスト' end it '一致するURI' do assert @uri == URI.parse('http://mikutter.hachune.net/') end it '一致するAddressableURI' do assert @uri == Addressable::URI.parse('http://mikutter.hachune.net/') end end end describe '非ASCII文字host' do before do @uri = Diva::URI('https://こっち.みんな/ておくれ') end it '文字列表現を得ることができる' do assert_equal 'https://こっち.みんな/ておくれ', @uri.to_s end describe '情報を得る' do it 'scheme' do assert_equal 'https', @uri.scheme end it 'host' do assert_equal 'こっち.みんな', @uri.host end it 'path' do assert_equal '/ておくれ', @uri.path end end describe '比較' do it '一致するString' do assert @uri == 'https://こっち.みんな/ておくれ' end it 'URIではないString' do refute @uri == 'テストテキスト' end it '一致するAddressableURI' do assert @uri == Addressable::URI.parse('https://こっち.みんな/ておくれ') end end end end