vastlint 0.4.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a98849d1f2a31ab7e93a306f409fc6447f122f1b45594d19c339ae3cb40270bc
4
+ data.tar.gz: 3261d865cfdc12e1f3cd776b4a8a68f97f55dcf2e1d70c27b5ddcb31e37047da
5
+ SHA512:
6
+ metadata.gz: 5c6cf05d772b448bd91d1351d9f0ab91a59c1ac582b843d8d7e593956c0f86688309742f4c0c8a24438f7f7160280dc0a5538139aaa21ca1f956f9ffbdcf3dca
7
+ data.tar.gz: 8336a6dd7af6762f49da095fb5ba41c2696bae88979e33a532ce85d34e75ae376e5cb25d4f255e12c176f847a657120ad28cb98f58c0d4c9f6f1eb0ea336740a
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/README.md ADDED
@@ -0,0 +1,194 @@
1
+ # vastlint-ruby
2
+
3
+ High-performance, in-process VAST XML validation for Ruby backends.
4
+
5
+ This gem wraps the existing `vastlint-ffi` C API, which is backed by the same Rust core used by the CLI, Go binding, Erlang NIF, MCP server, and web validator. The intended use case is a DSP, SSP, ad server, or trafficking backend that needs to validate VAST XML and return structured linting results to a React or other web frontend.
6
+
7
+ ## Why this shape
8
+
9
+ - No subprocess management in your Ruby app
10
+ - No network hop to an external validation service
11
+ - Stable JSON-compatible result shape for backend-to-frontend responses
12
+ - Same rule coverage and behavior as the rest of the vastlint ecosystem
13
+
14
+ ## Install
15
+
16
+ RubyGems.org is not currently usable for new gem registrations, so the supported distribution target for this package is GitHub Packages.
17
+
18
+ Authenticate Bundler against GitHub Packages:
19
+
20
+ ```sh
21
+ bundle config https://rubygems.pkg.github.com/aleksUIX USERNAME:TOKEN
22
+ ```
23
+
24
+ Then add the package source in your application:
25
+
26
+ ```ruby
27
+ source "https://rubygems.org"
28
+
29
+ source "https://rubygems.pkg.github.com/aleksUIX" do
30
+ gem "vastlint"
31
+ end
32
+ ```
33
+
34
+ Then install it:
35
+
36
+ ```sh
37
+ bundle install
38
+ ```
39
+
40
+ The gem expects a platform-matched `libvastlint` shared library.
41
+
42
+ For development in this monorepo, it will automatically try the sibling `vastlint/target/debug` and `vastlint/target/release` outputs.
43
+
44
+ For a distributable gem release, vendor the release libraries into `lib/vastlint/native/*` with:
45
+
46
+ ```sh
47
+ ./scripts/fetch-libs.sh v0.4.14
48
+ ```
49
+
50
+ You can also point the gem at an explicit shared library path:
51
+
52
+ ```sh
53
+ export VASTLINT_LIB_PATH=/absolute/path/to/libvastlint.dylib
54
+ ```
55
+
56
+ If you need anonymous public installs without GitHub auth, GitHub Packages is the wrong host. In that case, use a different gem server such as Gemfury, Cloudsmith, or a private Gemstash instance.
57
+
58
+ ## Usage
59
+
60
+ ```ruby
61
+ require "vastlint"
62
+
63
+ result = Vastlint.validate(vast_xml)
64
+
65
+ if result.valid?
66
+ puts "clean tag"
67
+ else
68
+ puts result.summary.errors
69
+ puts result.issues.first.message
70
+ end
71
+
72
+ puts result.to_json
73
+ ```
74
+
75
+ ### Rails controller example
76
+
77
+ ```ruby
78
+ class VastValidationsController < ApplicationController
79
+ def create
80
+ result = Vastlint.validate(
81
+ params.require(:xml),
82
+ wrapper_depth: params[:wrapper_depth].to_i,
83
+ max_wrapper_depth: params[:max_wrapper_depth].presence&.to_i || 5,
84
+ rule_overrides: params[:rule_overrides]
85
+ )
86
+
87
+ render json: result.as_json
88
+ rescue ArgumentError, Vastlint::Error => error
89
+ render json: { error: error.message }, status: :unprocessable_entity
90
+ end
91
+ end
92
+ ```
93
+
94
+ The response shape is stable and frontend-friendly:
95
+
96
+ ```json
97
+ {
98
+ "version": "4.2",
99
+ "issues": [
100
+ {
101
+ "id": "VAST-2.0-inline-impression",
102
+ "severity": "error",
103
+ "message": "<InLine> must contain at least one <Impression>",
104
+ "path": "/VAST/Ad[0]/InLine",
105
+ "spec_ref": "IAB VAST 2.0 §2.2.1",
106
+ "line": 4,
107
+ "col": 3
108
+ }
109
+ ],
110
+ "summary": {
111
+ "errors": 1,
112
+ "warnings": 0,
113
+ "infos": 0,
114
+ "valid": false
115
+ }
116
+ }
117
+ ```
118
+
119
+ ## API
120
+
121
+ ```ruby
122
+ Vastlint.validate(xml, wrapper_depth: 0, max_wrapper_depth: 5, rule_overrides: nil)
123
+ Vastlint.version
124
+ ```
125
+
126
+ `rule_overrides` accepts a hash of rule IDs to levels:
127
+
128
+ ```ruby
129
+ result = Vastlint.validate(
130
+ vast_xml,
131
+ rule_overrides: {
132
+ "VAST-2.0-mediafile-https" => "error",
133
+ "VAST-4.1-mezzanine-recommended" => "off"
134
+ }
135
+ )
136
+ ```
137
+
138
+ ## Native library layout
139
+
140
+ Vendored release libraries belong at:
141
+
142
+ - `lib/vastlint/native/darwin_arm64/libvastlint.dylib`
143
+ - `lib/vastlint/native/darwin_amd64/libvastlint.dylib`
144
+ - `lib/vastlint/native/linux_arm64/libvastlint.so`
145
+ - `lib/vastlint/native/linux_amd64/libvastlint.so`
146
+
147
+ The shared libraries come from the `vastlint-ffi-*` tarballs attached to each `vastlint` GitHub Release.
148
+
149
+ ## Publish
150
+
151
+ ### From GitHub Actions
152
+
153
+ If this gem lives in its own GitHub repository, you do not need to add a separate publish token just to push to GitHub Packages. GitHub injects `GITHUB_TOKEN` automatically, and the workflow only needs `packages: write` permission.
154
+
155
+ The included workflow at `.github/workflows/publish-github-packages.yml`:
156
+
157
+ - derives the gem version from the tag or `lib/vastlint/version.rb`
158
+ - downloads the matching `vastlint-ffi-*` shared libraries from the main `aleksUIX/vastlint` release
159
+ - runs the Ruby tests
160
+ - builds the gem
161
+ - pushes it to `rubygems.pkg.github.com/<owner>` using `GITHUB_TOKEN`
162
+
163
+ That means the normal release path can be fully automated from CI.
164
+
165
+ ### From a local machine
166
+
167
+ Publish to GitHub Packages with a classic personal access token that has `write:packages`.
168
+
169
+ ```sh
170
+ export GITHUB_PACKAGES_OWNER=aleksUIX
171
+ export GITHUB_PACKAGES_TOKEN=YOUR_CLASSIC_PAT
172
+ ./scripts/publish-github-packages.sh
173
+ ```
174
+
175
+ The script:
176
+
177
+ - builds `vastlint-<version>.gem` if needed
178
+ - writes a temporary `~/.gem/credentials` with `:github: Bearer TOKEN`
179
+ - pushes to `https://rubygems.pkg.github.com/$GITHUB_PACKAGES_OWNER`
180
+
181
+ The publish script also accepts `GITHUB_TOKEN`, so the exact same script works inside GitHub Actions without adding a separate secret.
182
+
183
+ Install from the registry with:
184
+
185
+ ```sh
186
+ gem install vastlint \
187
+ --clear-sources \
188
+ --source https://USERNAME:TOKEN@rubygems.pkg.github.com/aleksUIX/
189
+ ```
190
+
191
+ ## License
192
+
193
+ Apache 2.0.
194
+
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rake/testtask"
4
+
5
+ Rake::TestTask.new(:test) do |task|
6
+ task.libs << "lib"
7
+ task.pattern = "test/**/*_test.rb"
8
+ end
9
+
10
+ task default: :test
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vastlint
4
+ class Error < StandardError; end
5
+ class LibraryError < Error; end
6
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Vastlint
6
+ class Issue
7
+ attr_reader :id, :severity, :message, :path, :spec_ref, :line, :col
8
+
9
+ def initialize(id:, severity:, message:, path:, spec_ref:, line:, col:)
10
+ @id = id
11
+ @severity = severity
12
+ @message = message
13
+ @path = path
14
+ @spec_ref = spec_ref
15
+ @line = line
16
+ @col = col
17
+ end
18
+
19
+ def as_json(*)
20
+ {
21
+ id: id,
22
+ severity: severity,
23
+ message: message,
24
+ path: path,
25
+ spec_ref: spec_ref,
26
+ line: line,
27
+ col: col
28
+ }
29
+ end
30
+
31
+ def to_h
32
+ as_json
33
+ end
34
+
35
+ def to_json(*args)
36
+ JSON.generate(as_json, *args)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fiddle"
4
+ require "fiddle/import"
5
+ require "json"
6
+ require "rbconfig"
7
+ require "singleton"
8
+
9
+ module Vastlint
10
+ class Library
11
+ include Singleton
12
+
13
+ def validate(xml, wrapper_depth:, max_wrapper_depth:, rule_overrides:)
14
+ raw_result = if default_call?(wrapper_depth, max_wrapper_depth, rule_overrides)
15
+ api.vastlint_validate(xml, xml.bytesize)
16
+ else
17
+ api.vastlint_validate_with_options(
18
+ xml,
19
+ xml.bytesize,
20
+ wrapper_depth,
21
+ max_wrapper_depth,
22
+ serialize_rule_overrides(rule_overrides)
23
+ )
24
+ end
25
+
26
+ raise LibraryError, "vastlint returned NULL" if null_pointer?(raw_result)
27
+
28
+ begin
29
+ json_payload = read_c_string(api.vastlint_result_json(raw_result))
30
+ raise LibraryError, "vastlint_result_json returned NULL" if json_payload.nil? || json_payload.empty?
31
+
32
+ json_payload
33
+ ensure
34
+ api.vastlint_result_free(raw_result) unless null_pointer?(raw_result)
35
+ end
36
+ end
37
+
38
+ def version
39
+ read_c_string(api.vastlint_version()) || raise(LibraryError, "vastlint_version returned NULL")
40
+ end
41
+
42
+ def path
43
+ self.class.resolve_path
44
+ end
45
+
46
+ class << self
47
+ def resolve_path
48
+ candidates = [ENV["VASTLINT_LIB_PATH"], vendored_path, *development_paths].compact
49
+ match = candidates.find { |candidate| File.file?(candidate) }
50
+ return match if match
51
+
52
+ raise LibraryError, <<~MESSAGE.strip
53
+ unable to find libvastlint for #{platform_label}
54
+ looked in:
55
+ #{candidates.map { |candidate| " - #{candidate}" }.join("\n")}
56
+ set VASTLINT_LIB_PATH or vendor a release library under lib/vastlint/native
57
+ MESSAGE
58
+ end
59
+
60
+ private
61
+
62
+ def vendored_path
63
+ File.join(repo_root, "lib", "vastlint", "native", platform_directory, library_filename)
64
+ end
65
+
66
+ def development_paths
67
+ sibling_vastlint_root = File.expand_path("../vastlint", repo_root)
68
+ extension = shared_library_extension
69
+
70
+ [
71
+ File.join(sibling_vastlint_root, "target", "debug", "libvastlint_ffi.#{extension}"),
72
+ File.join(sibling_vastlint_root, "target", "release", "libvastlint_ffi.#{extension}")
73
+ ]
74
+ end
75
+
76
+ def repo_root
77
+ File.expand_path("../..", __dir__)
78
+ end
79
+
80
+ def library_filename
81
+ "libvastlint.#{shared_library_extension}"
82
+ end
83
+
84
+ def shared_library_extension
85
+ macos? ? "dylib" : "so"
86
+ end
87
+
88
+ def platform_directory
89
+ cpu = RbConfig::CONFIG.fetch("host_cpu")
90
+
91
+ if macos?
92
+ return "darwin_arm64" if cpu.match?(/arm64|aarch64/)
93
+ return "darwin_amd64" if cpu.match?(/x86_64|amd64/)
94
+ elsif linux?
95
+ return "linux_arm64" if cpu.match?(/arm64|aarch64/)
96
+ return "linux_amd64" if cpu.match?(/x86_64|amd64/)
97
+ end
98
+
99
+ raise LibraryError, "unsupported platform #{platform_label}"
100
+ end
101
+
102
+ def platform_label
103
+ "#{RbConfig::CONFIG.fetch("host_os")}/#{RbConfig::CONFIG.fetch("host_cpu")}"
104
+ end
105
+
106
+ def macos?
107
+ RbConfig::CONFIG.fetch("host_os").include?("darwin")
108
+ end
109
+
110
+ def linux?
111
+ RbConfig::CONFIG.fetch("host_os").include?("linux")
112
+ end
113
+ end
114
+
115
+ private
116
+
117
+ def api
118
+ @api ||= begin
119
+ library_path = self.class.resolve_path
120
+
121
+ Module.new.tap do |mod|
122
+ mod.extend(Fiddle::Importer)
123
+ mod.dlload(library_path)
124
+ mod.extern "void* vastlint_validate(const char*, size_t)"
125
+ mod.extern "void* vastlint_validate_with_options(const char*, size_t, unsigned int, unsigned int, const char*)"
126
+ mod.extern "char* vastlint_result_json(void*)"
127
+ mod.extern "void vastlint_result_free(void*)"
128
+ mod.extern "char* vastlint_version()"
129
+ end
130
+ rescue Fiddle::DLError => error
131
+ raise LibraryError, "failed to load #{library_path}: #{error.message}"
132
+ end
133
+ end
134
+
135
+ def default_call?(wrapper_depth, max_wrapper_depth, rule_overrides)
136
+ wrapper_depth.zero? && max_wrapper_depth == 5 && (rule_overrides.nil? || rule_overrides.empty?)
137
+ end
138
+
139
+ def serialize_rule_overrides(rule_overrides)
140
+ return nil if rule_overrides.nil? || rule_overrides.empty?
141
+
142
+ JSON.generate(rule_overrides)
143
+ end
144
+
145
+ def null_pointer?(pointer)
146
+ pointer.nil? || pointer.to_i.zero?
147
+ end
148
+
149
+ def read_c_string(value)
150
+ case value
151
+ when nil
152
+ nil
153
+ when String
154
+ value
155
+ else
156
+ Fiddle::Pointer.new(value.to_i).to_s
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,10 @@
1
+ Place release shared libraries here before publishing the gem.
2
+
3
+ Expected layout:
4
+
5
+ - darwin_arm64/libvastlint.dylib
6
+ - darwin_amd64/libvastlint.dylib
7
+ - linux_arm64/libvastlint.so
8
+ - linux_amd64/libvastlint.so
9
+
10
+ Use ../../scripts/fetch-libs.sh to download the platform tarballs from a tagged vastlint GitHub Release and copy the shared libraries into this directory.
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Vastlint
6
+ class Result
7
+ attr_reader :version, :issues, :summary
8
+
9
+ def self.from_json(json_payload)
10
+ parsed = JSON.parse(json_payload)
11
+
12
+ new(
13
+ version: parsed["version"],
14
+ issues: Array(parsed["issues"]).map do |issue|
15
+ Issue.new(
16
+ id: issue["id"],
17
+ severity: issue["severity"],
18
+ message: issue["message"],
19
+ path: issue["path"],
20
+ spec_ref: issue["spec_ref"],
21
+ line: issue["line"],
22
+ col: issue["col"]
23
+ )
24
+ end,
25
+ summary: Summary.new(
26
+ errors: parsed.fetch("summary").fetch("errors"),
27
+ warnings: parsed.fetch("summary").fetch("warnings"),
28
+ infos: parsed.fetch("summary").fetch("infos"),
29
+ valid: parsed.fetch("summary").fetch("valid")
30
+ )
31
+ )
32
+ rescue JSON::ParserError => error
33
+ raise LibraryError, "failed to parse vastlint result JSON: #{error.message}"
34
+ end
35
+
36
+ def initialize(version:, issues:, summary:)
37
+ @version = version
38
+ @issues = issues
39
+ @summary = summary
40
+ end
41
+
42
+ def valid?
43
+ summary.valid?
44
+ end
45
+
46
+ def as_json(*)
47
+ {
48
+ version: version,
49
+ issues: issues.map(&:as_json),
50
+ summary: summary.as_json
51
+ }
52
+ end
53
+
54
+ def to_h
55
+ as_json
56
+ end
57
+
58
+ def to_json(*args)
59
+ JSON.generate(as_json, *args)
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Vastlint
6
+ class Summary
7
+ attr_reader :errors, :warnings, :infos
8
+
9
+ def initialize(errors:, warnings:, infos:, valid:)
10
+ @errors = errors
11
+ @warnings = warnings
12
+ @infos = infos
13
+ @valid = valid
14
+ end
15
+
16
+ def valid?
17
+ @valid
18
+ end
19
+
20
+ def as_json(*)
21
+ {
22
+ errors: errors,
23
+ warnings: warnings,
24
+ infos: infos,
25
+ valid: valid?
26
+ }
27
+ end
28
+
29
+ def to_h
30
+ as_json
31
+ end
32
+
33
+ def to_json(*args)
34
+ JSON.generate(as_json, *args)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vastlint
4
+ VERSION = "0.4.14"
5
+ end
data/lib/vastlint.rb ADDED
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "vastlint/version"
4
+ require_relative "vastlint/error"
5
+ require_relative "vastlint/library"
6
+ require_relative "vastlint/issue"
7
+ require_relative "vastlint/summary"
8
+ require_relative "vastlint/result"
9
+
10
+ module Vastlint
11
+ class << self
12
+ def validate(xml, wrapper_depth: 0, max_wrapper_depth: 5, rule_overrides: nil)
13
+ normalized_xml = normalize_xml(xml)
14
+ validate_options!(wrapper_depth, max_wrapper_depth, rule_overrides)
15
+
16
+ Result.from_json(
17
+ Library.instance.validate(
18
+ normalized_xml,
19
+ wrapper_depth: wrapper_depth,
20
+ max_wrapper_depth: max_wrapper_depth,
21
+ rule_overrides: rule_overrides
22
+ )
23
+ )
24
+ end
25
+
26
+ def version
27
+ Library.instance.version
28
+ end
29
+
30
+ private
31
+
32
+ def normalize_xml(xml)
33
+ raise ArgumentError, "xml must be a String" unless xml.is_a?(String)
34
+ raise ArgumentError, "xml must not be empty" if xml.empty?
35
+
36
+ xml
37
+ end
38
+
39
+ def validate_options!(wrapper_depth, max_wrapper_depth, rule_overrides)
40
+ unless wrapper_depth.is_a?(Integer) && wrapper_depth >= 0
41
+ raise ArgumentError, "wrapper_depth must be an Integer >= 0"
42
+ end
43
+
44
+ unless max_wrapper_depth.is_a?(Integer) && max_wrapper_depth >= 0
45
+ raise ArgumentError, "max_wrapper_depth must be an Integer >= 0"
46
+ end
47
+
48
+ return if rule_overrides.nil? || rule_overrides.is_a?(Hash)
49
+
50
+ raise ArgumentError, "rule_overrides must be a Hash or nil"
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -euo pipefail
4
+
5
+ REPO="aleksUIX/vastlint"
6
+ RELEASE_TAG="${1:-}"
7
+
8
+ if [[ -z "$RELEASE_TAG" ]]; then
9
+ echo "Usage: $0 <release-tag> (e.g. v0.4.14)" >&2
10
+ exit 1
11
+ fi
12
+
13
+ BASE_URL="https://github.com/${REPO}/releases/download/${RELEASE_TAG}"
14
+
15
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
16
+ REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
17
+ TMPDIR="$(mktemp -d)"
18
+ trap 'rm -rf "$TMPDIR"' EXIT
19
+
20
+ while IFS='|' read -r TARBALL PLATFORM_DIR LIB_NAME; do
21
+ [[ -n "$TARBALL" ]] || continue
22
+
23
+ URL="${BASE_URL}/${TARBALL}"
24
+ DEST_DIR="${REPO_ROOT}/lib/vastlint/native/${PLATFORM_DIR}"
25
+ UNPACK_DIR="${TMPDIR}/${PLATFORM_DIR}"
26
+
27
+ echo "→ Downloading ${TARBALL}"
28
+ mkdir -p "$UNPACK_DIR" "$DEST_DIR"
29
+ curl -fsSL "$URL" -o "${TMPDIR}/${TARBALL}"
30
+ tar xzf "${TMPDIR}/${TARBALL}" -C "$UNPACK_DIR"
31
+
32
+ cp "${UNPACK_DIR}/${LIB_NAME}" "${DEST_DIR}/${LIB_NAME}"
33
+ chmod 755 "${DEST_DIR}/${LIB_NAME}"
34
+ echo " ✓ ${DEST_DIR}/${LIB_NAME}"
35
+ done <<'PLATFORMS'
36
+ vastlint-ffi-macos-aarch64.tar.gz|darwin_arm64|libvastlint.dylib
37
+ vastlint-ffi-macos-x86_64.tar.gz|darwin_amd64|libvastlint.dylib
38
+ vastlint-ffi-linux-aarch64.tar.gz|linux_arm64|libvastlint.so
39
+ vastlint-ffi-linux-x86_64.tar.gz|linux_amd64|libvastlint.so
40
+ PLATFORMS
41
+
42
+ echo
43
+ echo "Done. Vendored shared libraries updated to ${RELEASE_TAG}."
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -euo pipefail
4
+
5
+ OWNER="${GITHUB_PACKAGES_OWNER:-${GITHUB_REPOSITORY_OWNER:-aleksUIX}}"
6
+ TOKEN="${GITHUB_PACKAGES_TOKEN:-${GITHUB_TOKEN:-}}"
7
+
8
+ if [[ -z "$TOKEN" ]]; then
9
+ echo "GITHUB_PACKAGES_TOKEN or GITHUB_TOKEN is required" >&2
10
+ echo "Use GITHUB_TOKEN in GitHub Actions, or a classic GitHub personal access token with write:packages locally" >&2
11
+ exit 1
12
+ fi
13
+
14
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
15
+ REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
16
+
17
+ cd "$REPO_ROOT"
18
+
19
+ VERSION="$(ruby -Ilib -e 'require "vastlint/version"; print Vastlint::VERSION')"
20
+ GEM_FILE="vastlint-${VERSION}.gem"
21
+
22
+ if [[ ! -f "$GEM_FILE" ]]; then
23
+ gem build vastlint.gemspec
24
+ fi
25
+
26
+ TMP_HOME="$(mktemp -d)"
27
+ trap 'rm -rf "$TMP_HOME"' EXIT
28
+
29
+ mkdir -p "$TMP_HOME/.gem"
30
+ chmod 700 "$TMP_HOME/.gem"
31
+
32
+ cat > "$TMP_HOME/.gem/credentials" <<EOF
33
+ ---
34
+ :github: Bearer ${TOKEN}
35
+ EOF
36
+
37
+ chmod 600 "$TMP_HOME/.gem/credentials"
38
+
39
+ HOME="$TMP_HOME" gem push \
40
+ --key github \
41
+ --host "https://rubygems.pkg.github.com/${OWNER}" \
42
+ "$GEM_FILE"
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -euo pipefail
4
+
5
+ API_KEY="${RUBYGEMS_API_KEY:-}"
6
+
7
+ if [[ -z "$API_KEY" ]]; then
8
+ echo "RUBYGEMS_API_KEY is required (an API key from https://rubygems.org/profile/api_keys with Push rights)" >&2
9
+ exit 1
10
+ fi
11
+
12
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
+ REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
14
+
15
+ cd "$REPO_ROOT"
16
+
17
+ VERSION="$(ruby -Ilib -e 'require "vastlint/version"; print Vastlint::VERSION')"
18
+ GEM_FILE="vastlint-${VERSION}.gem"
19
+
20
+ if [[ ! -f "$GEM_FILE" ]]; then
21
+ gem build vastlint.gemspec
22
+ fi
23
+
24
+ TMP_HOME="$(mktemp -d)"
25
+ trap 'rm -rf "$TMP_HOME"' EXIT
26
+
27
+ mkdir -p "$TMP_HOME/.gem"
28
+ chmod 700 "$TMP_HOME/.gem"
29
+
30
+ cat > "$TMP_HOME/.gem/credentials" <<EOF
31
+ ---
32
+ :rubygems_api_key: ${API_KEY}
33
+ EOF
34
+
35
+ chmod 600 "$TMP_HOME/.gem/credentials"
36
+
37
+ HOME="$TMP_HOME" gem push "$GEM_FILE"
@@ -0,0 +1,18 @@
1
+ <VAST version="4.2">
2
+ <Ad>
3
+ <InLine>
4
+ <AdSystem>Broken DSP Creative</AdSystem>
5
+ <AdTitle>Invalid Ad</AdTitle>
6
+ <Creatives>
7
+ <Creative>
8
+ <Linear>
9
+ <Duration>30</Duration>
10
+ <MediaFiles>
11
+ <MediaFile delivery="progressive" type="video/mp4" width="640" height="360">http://example.com/video.mp4</MediaFile>
12
+ </MediaFiles>
13
+ </Linear>
14
+ </Creative>
15
+ </Creatives>
16
+ </InLine>
17
+ </Ad>
18
+ </VAST>
@@ -0,0 +1,19 @@
1
+ <VAST version="2.0">
2
+ <Ad>
3
+ <InLine>
4
+ <AdSystem>Test</AdSystem>
5
+ <AdTitle>Test Ad</AdTitle>
6
+ <Impression><![CDATA[https://example.com/pixel]]></Impression>
7
+ <Creatives>
8
+ <Creative>
9
+ <Linear>
10
+ <Duration>00:00:30</Duration>
11
+ <MediaFiles>
12
+ <MediaFile delivery="progressive" type="video/mp4" width="640" height="360"><![CDATA[https://example.com/video.mp4]]></MediaFile>
13
+ </MediaFiles>
14
+ </Linear>
15
+ </Creative>
16
+ </Creatives>
17
+ </InLine>
18
+ </Ad>
19
+ </VAST>
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "minitest/autorun"
4
+ require_relative "../lib/vastlint"
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "test_helper"
4
+
5
+ class VastlintTest < Minitest::Test
6
+ def test_version_returns_a_string
7
+ refute_empty Vastlint.version
8
+ end
9
+
10
+ def test_valid_fixture_returns_no_errors
11
+ result = Vastlint.validate(read_fixture("valid.xml"))
12
+
13
+ assert result.valid?
14
+ assert_equal 0, result.summary.errors
15
+ assert_equal true, result.summary.valid?
16
+ assert_equal "2.0", result.version
17
+ end
18
+
19
+ def test_invalid_fixture_returns_structured_issues
20
+ result = Vastlint.validate(read_fixture("invalid.xml"))
21
+
22
+ refute result.valid?
23
+ assert_operator result.summary.errors, :>, 0
24
+ assert_operator result.issues.length, :>, 0
25
+ assert_includes %w[error warning info], result.issues.first.severity
26
+ assert_kind_of Hash, result.as_json
27
+ end
28
+
29
+ private
30
+
31
+ def read_fixture(name)
32
+ File.read(File.expand_path("fixtures/#{name}", __dir__))
33
+ end
34
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vastlint
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.14
5
+ platform: ruby
6
+ authors:
7
+ - Alex Sekowski
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2026-07-03 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Ruby bindings for the vastlint Rust core via the stable C FFI. Validate
14
+ VAST XML in a DSP backend and return structured results directly to a React frontend.
15
+ email:
16
+ - alex@vastlint.org
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - Gemfile
22
+ - README.md
23
+ - Rakefile
24
+ - lib/vastlint.rb
25
+ - lib/vastlint/error.rb
26
+ - lib/vastlint/issue.rb
27
+ - lib/vastlint/library.rb
28
+ - lib/vastlint/native/README.md
29
+ - lib/vastlint/native/darwin_amd64/libvastlint.dylib
30
+ - lib/vastlint/native/darwin_arm64/libvastlint.dylib
31
+ - lib/vastlint/native/linux_amd64/libvastlint.so
32
+ - lib/vastlint/native/linux_arm64/libvastlint.so
33
+ - lib/vastlint/result.rb
34
+ - lib/vastlint/summary.rb
35
+ - lib/vastlint/version.rb
36
+ - scripts/fetch-libs.sh
37
+ - scripts/publish-github-packages.sh
38
+ - scripts/publish-rubygems.sh
39
+ - test/fixtures/invalid.xml
40
+ - test/fixtures/valid.xml
41
+ - test/test_helper.rb
42
+ - test/vastlint_test.rb
43
+ homepage: https://vastlint.org
44
+ licenses:
45
+ - Apache-2.0
46
+ metadata:
47
+ homepage_uri: https://vastlint.org
48
+ source_code_uri: https://github.com/aleksUIX/vastlint-ruby
49
+ changelog_uri: https://github.com/aleksUIX/vastlint-ruby/releases
50
+ documentation_uri: https://vastlint.org/docs/rules
51
+ github_repo: ssh://github.com/aleksUIX/vastlint-ruby
52
+ post_install_message:
53
+ rdoc_options: []
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '3.1'
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ requirements: []
67
+ rubygems_version: 3.5.22
68
+ signing_key:
69
+ specification_version: 4
70
+ summary: In-process Ruby bindings for vastlint VAST XML validation
71
+ test_files: []