Skip to content

Commit f270bc8

Browse files
committed
Initial commit
0 parents  commit f270bc8

42 files changed

Lines changed: 2954 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/elixir.yml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
name: Elixir CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
lint:
11+
name: Lint (OTP ${{matrix.otp}} / Elixir ${{matrix.elixir}})
12+
runs-on: ubuntu-latest
13+
strategy:
14+
matrix:
15+
otp: ["27"]
16+
elixir: ["1.18"]
17+
steps:
18+
- uses: actions/checkout@v4
19+
- uses: erlef/setup-beam@v1
20+
id: beam
21+
with:
22+
otp-version: ${{matrix.otp}}
23+
elixir-version: ${{matrix.elixir}}
24+
- name: PLT cache
25+
uses: actions/cache@v4
26+
with:
27+
key: |
28+
${{ runner.os }}-${{ steps.beam.outputs.elixir-version }}-${{ steps.beam.outputs.otp-version }}-plt
29+
restore-keys: |
30+
${{ runner.os }}-${{ steps.beam.outputs.elixir-version }}-${{ steps.beam.outputs.otp-version }}-plt
31+
path: |
32+
priv/plts
33+
- run: mix deps.get
34+
- run: mix compile --warnings-as-errors
35+
- run: mix format --check-formatted
36+
- run: mix credo --strict --all
37+
- run: mix dialyzer --format github
38+
39+
test:
40+
runs-on: ubuntu-20.04
41+
name: Test (OTP ${{matrix.otp}} / Elixir ${{matrix.elixir}})
42+
strategy:
43+
matrix:
44+
otp: ["27"]
45+
elixir: ["1.18"]
46+
# Test each elixir version with lowest and highest compatible OTP version, exclude others
47+
# See https://hexdocs.pm/elixir/compatibility-and-deprecations.html#between-elixir-and-erlang-otp
48+
steps:
49+
- uses: actions/checkout@v4
50+
- uses: erlef/setup-beam@v1
51+
with:
52+
otp-version: ${{matrix.otp}}
53+
elixir-version: ${{matrix.elixir}}
54+
- uses: actions/cache@v4
55+
with:
56+
key: |
57+
${{ runner.os }}-${{ steps.beam.outputs.elixir-version }}-${{ steps.beam.outputs.otp-version }}-build
58+
restore-keys: |
59+
${{ runner.os }}-${{ steps.beam.outputs.elixir-version }}-${{ steps.beam.outputs.otp-version }}-build
60+
path: |
61+
_build
62+
- run: mix deps.get
63+
- run: mix test

.github/workflows/release.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: Release
2+
3+
on:
4+
release:
5+
types:
6+
- released
7+
8+
jobs:
9+
publish:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- name: Check out
13+
uses: actions/checkout@v2
14+
15+
- uses: erlef/setup-beam@v1
16+
id: beam
17+
with:
18+
otp-version: 25
19+
elixir-version: 1.14
20+
21+
- id: deps
22+
name: Fetch and compile dependencies
23+
run: |
24+
mix do deps.get, deps.compile
25+
26+
- id: build
27+
name: Build package
28+
run: |
29+
mix hex.build
30+
31+
- id: publish
32+
name: Publish package to hex.pm
33+
env:
34+
HEX_API_KEY: ${{ secrets.HEX_API_KEY }}
35+
run: |
36+
mix hex.publish --yes

.gitignore

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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+
# Ignore package tarball (built via "mix hex.build").
23+
api_spectre-*.tar
24+
25+
# Temporary files for e.g. tests.
26+
/tmp
27+
28+
# IDE / plugin files
29+
.elixir_ls/
30+
.vscode/
31+
.idea
32+
*.iml
33+
34+
/priv/plts

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## v0.1.0 - 2025-04-20

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Mike Buhot
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# API Spectre
2+
3+
<img src="./api_spectre_logo.png" width="200">
4+
5+
## Copyright and License
6+
7+
Copyright (c) 2025 Mike Buhot
8+
9+
Licensed under the MIT License, which can be found in [LICENSE](./LICENSE).

api_spectre_logo.png

1.48 MB
Loading

config/config.exs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# This file is responsible for configuring your application
2+
# and its dependencies with the aid of the Mix.Config module.
3+
import 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 your application as:
12+
#
13+
# config :open_api_spex, key: :value
14+
#
15+
# and access this configuration in your application as:
16+
#
17+
# Application.get_env(:open_api_spex, :key)
18+
#
19+
# You can also configure a 3rd-party app:
20+
#
21+
# config :logger, level: :info
22+
#
23+
24+
config :phoenix, json_library: Jason
25+
26+
if Mix.env() == :test do
27+
import_config "test.exs"
28+
end

config/test.exs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import Config
2+
3+
config :logger, level: :info

lib/api_spectre.ex

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
defmodule ApiSpectre do
2+
@moduledoc """
3+
TODO:
4+
- Functions for building a complete OpenAPI Description from routes
5+
"""
6+
alias ApiSpectre.OpenApi.Operation
7+
alias ApiSpectre.OpenApi.PathItem
8+
@spec add_router(ApiSpectre.OpenApi.t(), module()) :: ApiSpectre.OpenApi.t()
9+
def add_router(%ApiSpectre.OpenApi{} = api, router) do
10+
add_routes(api, router.__routes__())
11+
end
12+
13+
@spec add_routes(ApiSpectre.OpenApi.t(), list()) :: ApiSpectre.OpenApi.t()
14+
def add_routes(%ApiSpectre.OpenApi{} = api, routes) do
15+
paths =
16+
routes
17+
|> Enum.group_by(fn route -> route.path end)
18+
|> Enum.flat_map(fn {path, route_group} ->
19+
case make_path_item(route_group) do
20+
{:ok, %PathItem{} = path_item} -> [{open_api_path(path), path_item}]
21+
_ -> []
22+
end
23+
end)
24+
|> Map.new()
25+
26+
put_in(api.paths, Map.merge(api.paths, paths))
27+
end
28+
29+
@spec add_schemas(ApiSpectre.OpenApi.t(), map()) :: ApiSpectre.OpenApi.t()
30+
def add_schemas(%ApiSpectre.OpenApi{} = api, extra_schemas) do
31+
schemas = get_in(api.components.schemas) || %{}
32+
schemas = Map.merge(schemas, extra_schemas)
33+
put_in(api.components.schemas, schemas)
34+
end
35+
36+
def make_path_item(route_group) do
37+
Enum.each(route_group, fn route ->
38+
Code.ensure_loaded(route.plug)
39+
end)
40+
41+
route_group
42+
|> Enum.filter(&function_exported?(&1.plug, :open_api_operation, 1))
43+
|> case do
44+
[] ->
45+
{:error, :no_operations}
46+
47+
valid_routes ->
48+
valid_routes
49+
|> Enum.flat_map(fn route ->
50+
case make_operation(route) do
51+
%Operation{} = op -> [{route.verb, op}]
52+
_ -> []
53+
end
54+
end)
55+
|> case do
56+
[] -> {:error, :no_operations}
57+
attrs -> {:ok, struct!(PathItem, attrs)}
58+
end
59+
end
60+
end
61+
62+
def make_operation(route) do
63+
route.plug.open_api_operation(route.plug_opts)
64+
end
65+
66+
@spec open_api_path(String.t()) :: String.t()
67+
defp open_api_path(path) do
68+
path
69+
|> String.split("/")
70+
|> Enum.map_join("/", fn
71+
":" <> segment -> "{#{segment}}"
72+
segment -> segment
73+
end)
74+
end
75+
end

0 commit comments

Comments
 (0)