generated from musicparty/template
Compare commits
2 Commits
cf3afa35c3
...
feature/pa
| Author | SHA1 | Date | |
|---|---|---|---|
|
72f6bcc5be
|
|||
|
749dd0dbb2
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target
|
||||
21
Cargo.lock
generated
Normal file
21
Cargo.lock
generated
Normal file
@@ -0,0 +1,21 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||
|
||||
[[package]]
|
||||
name = "apidoc"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"apidoc_parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "apidoc_parser"
|
||||
version = "0.1.0"
|
||||
11
Cargo.toml
Normal file
11
Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
members = ["src/apidoc", "src/apidoc_parser"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
public = false
|
||||
|
||||
[workspace.clippy]
|
||||
strict = "warn"
|
||||
54
README.md
54
README.md
@@ -1,3 +1,53 @@
|
||||
# Musicparty management repository template
|
||||
# Apidoc
|
||||
|
||||
This is a template repository, use it to create other repositories.
|
||||
A code utility to manage your API:s, purely derrived from code (doc-comments).
|
||||
|
||||
## Trivial example
|
||||
|
||||
````ts
|
||||
// @apidoc TYPE User.Token
|
||||
// [readonly] [auth]
|
||||
//
|
||||
// A JWT token used to verify identity at secured endpoints.
|
||||
//
|
||||
// ## Schema
|
||||
// string.jwt
|
||||
//
|
||||
// ## Examples
|
||||
//
|
||||
// ```
|
||||
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30
|
||||
// ```
|
||||
type Token = string;
|
||||
|
||||
// @apidoc POST /auth/register Sign-in as user `User.login`
|
||||
//
|
||||
// Using credentials and details, request that a `User` be created and stored,
|
||||
// returning a `User.token`.
|
||||
//
|
||||
// ## Body JSON
|
||||
// username: string.ascii(max: 32) [unique] [example = `tommylive`]
|
||||
// email: string.email The email to use when authenticating the user.
|
||||
// password: string(min: 8) [sendonly]
|
||||
// A plain-text password to hash and store for authentication.
|
||||
//
|
||||
// Password criteria:
|
||||
// - >8 characters
|
||||
// - >1 uppercase ascii letter
|
||||
// - >1 symbol, defined as any non-ascii-alphanumeric character
|
||||
// display_name?: string(max: 32) [example = `Tommy`]
|
||||
// An alternative name to show instead of the username
|
||||
//
|
||||
// ## Responses
|
||||
// ### 200 User created JSON
|
||||
//
|
||||
// The user passed checks and was created.
|
||||
//
|
||||
// token: User.Token Token to identify as the newly created user
|
||||
//
|
||||
// ### 400 Invalid payload
|
||||
// ### 409 Username taken
|
||||
export async function processRegister(req: Request, res: Response) {
|
||||
// ...
|
||||
}
|
||||
````
|
||||
|
||||
8
src/apidoc/Cargo.toml
Normal file
8
src/apidoc/Cargo.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "apidoc"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.100"
|
||||
apidoc_parser = { version = "0.1.0", path = "../apidoc_parser" }
|
||||
17
src/apidoc/src/main.rs
Normal file
17
src/apidoc/src/main.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use std::{env::args, fs};
|
||||
|
||||
use anyhow::Context;
|
||||
use apidoc_parser::parse;
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let mut args = args().skip(1);
|
||||
|
||||
let file = fs::read_to_string(args.next().context("expected a file path argument")?)
|
||||
.context("opening file")?;
|
||||
|
||||
println!("{file}");
|
||||
|
||||
parse(file);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
7
src/apidoc_parser/Cargo.toml
Normal file
7
src/apidoc_parser/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "apidoc_parser"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
public.workspace = true
|
||||
|
||||
[dependencies]
|
||||
20
src/apidoc_parser/src/comment.rs
Normal file
20
src/apidoc_parser/src/comment.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Span {
|
||||
lo: usize,
|
||||
hi: usize,
|
||||
}
|
||||
|
||||
impl Span {
|
||||
pub const fn new(range: std::ops::Range<usize>) -> Self {
|
||||
Self {
|
||||
lo: range.start,
|
||||
hi: range.end,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Comment {
|
||||
pub(crate) prefix: String,
|
||||
pub(crate) span: Span,
|
||||
}
|
||||
75
src/apidoc_parser/src/cursor.rs
Normal file
75
src/apidoc_parser/src/cursor.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Cursor {
|
||||
source: String,
|
||||
offset: usize,
|
||||
byte_offset: usize,
|
||||
}
|
||||
|
||||
impl Cursor {
|
||||
pub const fn new(source: String) -> Self {
|
||||
Self {
|
||||
source,
|
||||
offset: 0,
|
||||
byte_offset: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn slice(&self) -> &'_ str {
|
||||
self.source
|
||||
.get(self.byte_offset..)
|
||||
.expect("str should be built out of bounded chars")
|
||||
}
|
||||
|
||||
pub fn bump(&mut self) -> &mut Self {
|
||||
self.offset += 1;
|
||||
self.byte_offset += self.slice().chars().next().map_or(0, char::len_utf8);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn bump_newline(&mut self) -> bool {
|
||||
while !self.slice().starts_with('\n') && !self.bump().is_finished() {}
|
||||
|
||||
if self.slice().starts_with('\n') {
|
||||
self.bump();
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn skip(&mut self, chars: usize) {
|
||||
let len: usize = self.slice().chars().take(chars).map(char::len_utf8).sum();
|
||||
|
||||
self.offset += chars;
|
||||
self.byte_offset += len;
|
||||
}
|
||||
|
||||
pub fn skip_whitespace(&mut self) {
|
||||
let mut offset = 0;
|
||||
let mut byte_offset = 0;
|
||||
|
||||
for char in self.slice().chars() {
|
||||
if !char.is_ascii_whitespace() || char == '\n' {
|
||||
break;
|
||||
}
|
||||
|
||||
offset += 1;
|
||||
byte_offset += char.len_utf8();
|
||||
}
|
||||
|
||||
self.offset += offset;
|
||||
self.byte_offset += byte_offset;
|
||||
}
|
||||
|
||||
pub const fn is_finished(&self) -> bool {
|
||||
self.byte_offset >= self.source.len()
|
||||
}
|
||||
|
||||
pub const fn offset(&self) -> usize {
|
||||
self.offset
|
||||
}
|
||||
|
||||
pub fn fmt_offset(&self) -> String {
|
||||
format!("{} (byte {})", self.offset, self.byte_offset)
|
||||
}
|
||||
}
|
||||
55
src/apidoc_parser/src/lib.rs
Normal file
55
src/apidoc_parser/src/lib.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use crate::comment::{Comment, Span};
|
||||
|
||||
pub mod comment;
|
||||
pub(crate) mod cursor;
|
||||
|
||||
use cursor::Cursor;
|
||||
|
||||
// TODO: (issue #3) Move these into some variable configuration
|
||||
const APIDOC_PRAGMA: &str = "@apidoc";
|
||||
const COMMENT_PREFIX: &str = "//";
|
||||
|
||||
pub fn parse(source: String) -> Vec<Comment> {
|
||||
let mut comments = Vec::new();
|
||||
|
||||
let mut cursor = Cursor::new(source);
|
||||
|
||||
while !cursor.bump().is_finished() {
|
||||
let slice = cursor.slice();
|
||||
|
||||
if slice.starts_with(COMMENT_PREFIX) {
|
||||
println!("comment: at offset {}", cursor.fmt_offset());
|
||||
|
||||
let start = cursor.offset();
|
||||
|
||||
cursor.skip(COMMENT_PREFIX.len());
|
||||
cursor.skip_whitespace();
|
||||
if !cursor.slice().starts_with(APIDOC_PRAGMA) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let prefix = COMMENT_PREFIX;
|
||||
|
||||
println!(" pragma! {}", cursor.fmt_offset());
|
||||
|
||||
cursor.bump_newline();
|
||||
let mut end = cursor.offset() - 1;
|
||||
|
||||
loop {
|
||||
cursor.skip_whitespace();
|
||||
if !cursor.slice().starts_with(prefix) {
|
||||
break;
|
||||
}
|
||||
cursor.bump_newline();
|
||||
end = cursor.offset() - 1;
|
||||
}
|
||||
|
||||
comments.push(dbg!(Comment {
|
||||
prefix: COMMENT_PREFIX.to_owned(),
|
||||
span: Span::new(start..end)
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
comments
|
||||
}
|
||||
Reference in New Issue
Block a user