mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-05-19 14:24:47 -07:00
global: fixes
This commit is contained in:
@@ -16,9 +16,13 @@ pub fn generate_api_methods(output: &mut String, endpoints: &[Endpoint]) {
|
||||
}
|
||||
|
||||
let method_name = endpoint_to_method_name(endpoint);
|
||||
let base_return_type = jsdoc_normalize(&normalize_return_type(
|
||||
endpoint.response_type.as_deref().unwrap_or("*"),
|
||||
));
|
||||
let base_return_type = if endpoint.returns_binary() {
|
||||
"Uint8Array".to_string()
|
||||
} else {
|
||||
jsdoc_normalize(&normalize_return_type(
|
||||
endpoint.schema_name().unwrap_or("*"),
|
||||
))
|
||||
};
|
||||
let return_type = if endpoint.supports_csv {
|
||||
format!("{} | string", base_return_type)
|
||||
} else {
|
||||
@@ -86,10 +90,14 @@ pub fn generate_api_methods(output: &mut String, endpoints: &[Endpoint]) {
|
||||
|
||||
let path = build_path_template(&endpoint.path, &endpoint.path_params);
|
||||
|
||||
let fetch_call = if endpoint.returns_json() {
|
||||
"this.getJson(path, { signal, onValue })"
|
||||
let fetch_call: String = if endpoint.returns_binary() {
|
||||
"this.getBytes(path, { signal, onValue })".to_string()
|
||||
} else if endpoint.returns_json() {
|
||||
"this.getJson(path, { signal, onValue })".to_string()
|
||||
} else if endpoint.response_kind.text_is_numeric() {
|
||||
"Number(await this.getText(path, { signal, onValue }))".to_string()
|
||||
} else {
|
||||
"this.getText(path, { signal, onValue })"
|
||||
"this.getText(path, { signal, onValue })".to_string()
|
||||
};
|
||||
|
||||
if endpoint.query_params.is_empty() {
|
||||
@@ -98,7 +106,15 @@ pub fn generate_api_methods(output: &mut String, endpoints: &[Endpoint]) {
|
||||
writeln!(output, " const params = new URLSearchParams();").unwrap();
|
||||
for param in &endpoint.query_params {
|
||||
let ident = sanitize_ident(¶m.name);
|
||||
if param.required {
|
||||
let is_array = param.param_type.ends_with("[]");
|
||||
if is_array {
|
||||
writeln!(
|
||||
output,
|
||||
" for (const _v of {}) params.append('{}', String(_v));",
|
||||
ident, param.name
|
||||
)
|
||||
.unwrap();
|
||||
} else if param.required {
|
||||
writeln!(
|
||||
output,
|
||||
" params.set('{}', String({}));",
|
||||
|
||||
@@ -558,6 +558,17 @@ class BrkClientBase {{
|
||||
return this._getCached(path, (res) => res.text(), options);
|
||||
}}
|
||||
|
||||
/**
|
||||
* Make a GET request expecting binary data (application/octet-stream).
|
||||
* Cached and supports `onValue`, same as `getJson`.
|
||||
* @param {{string}} path
|
||||
* @param {{{{ onValue?: (value: Uint8Array) => void, signal?: AbortSignal }}}} [options]
|
||||
* @returns {{Promise<Uint8Array>}}
|
||||
*/
|
||||
getBytes(path, options) {{
|
||||
return this._getCached(path, async (res) => new Uint8Array(await res.arrayBuffer()), options);
|
||||
}}
|
||||
|
||||
/**
|
||||
* Fetch series data and wrap with helper methods (internal)
|
||||
* @template T
|
||||
|
||||
@@ -96,13 +96,16 @@ pub fn generate_api_methods(output: &mut String, endpoints: &[Endpoint]) {
|
||||
}
|
||||
|
||||
let method_name = endpoint_to_method_name(endpoint);
|
||||
let base_return_type = normalize_return_type(
|
||||
&endpoint
|
||||
.response_type
|
||||
.as_deref()
|
||||
.map(js_type_to_python)
|
||||
.unwrap_or_else(|| "str".to_string()),
|
||||
);
|
||||
let base_return_type = if endpoint.returns_binary() {
|
||||
"bytes".to_string()
|
||||
} else {
|
||||
normalize_return_type(
|
||||
&endpoint
|
||||
.schema_name()
|
||||
.map(js_type_to_python)
|
||||
.unwrap_or_else(|| "str".to_string()),
|
||||
)
|
||||
};
|
||||
|
||||
let return_type = if endpoint.supports_csv {
|
||||
format!("Union[{}, str]", base_return_type)
|
||||
@@ -159,24 +162,50 @@ pub fn generate_api_methods(output: &mut String, endpoints: &[Endpoint]) {
|
||||
// Build path
|
||||
let path = build_path_template(&endpoint.path, &endpoint.path_params);
|
||||
|
||||
let fetch_method = if endpoint.returns_json() {
|
||||
let fetch_method = if endpoint.returns_binary() {
|
||||
"get"
|
||||
} else if endpoint.returns_json() {
|
||||
"get_json"
|
||||
} else {
|
||||
"get_text"
|
||||
};
|
||||
|
||||
let (wrap_prefix, wrap_suffix) = if endpoint.response_kind.text_is_numeric() {
|
||||
("int(", ")")
|
||||
} else {
|
||||
("", "")
|
||||
};
|
||||
|
||||
if endpoint.query_params.is_empty() {
|
||||
if endpoint.path_params.is_empty() {
|
||||
writeln!(output, " return self.{}('{}')", fetch_method, path).unwrap();
|
||||
writeln!(
|
||||
output,
|
||||
" return {}self.{}('{}'){}",
|
||||
wrap_prefix, fetch_method, path, wrap_suffix
|
||||
)
|
||||
.unwrap();
|
||||
} else {
|
||||
writeln!(output, " return self.{}(f'{}')", fetch_method, path).unwrap();
|
||||
writeln!(
|
||||
output,
|
||||
" return {}self.{}(f'{}'){}",
|
||||
wrap_prefix, fetch_method, path, wrap_suffix
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
} else {
|
||||
writeln!(output, " params = []").unwrap();
|
||||
for param in &endpoint.query_params {
|
||||
// Use safe name for Python variable, original name for API query parameter
|
||||
let safe_name = escape_python_keyword(¶m.name);
|
||||
if param.required {
|
||||
let is_array = param.param_type.ends_with("[]");
|
||||
if is_array {
|
||||
writeln!(
|
||||
output,
|
||||
" for _v in {}: params.append(f'{}={{_v}}')",
|
||||
safe_name, param.name
|
||||
)
|
||||
.unwrap();
|
||||
} else if param.required {
|
||||
writeln!(
|
||||
output,
|
||||
" params.append(f'{}={{{}}}')",
|
||||
@@ -203,9 +232,19 @@ pub fn generate_api_methods(output: &mut String, endpoints: &[Endpoint]) {
|
||||
if endpoint.supports_csv {
|
||||
writeln!(output, " if format == 'csv':").unwrap();
|
||||
writeln!(output, " return self.get_text(path)").unwrap();
|
||||
writeln!(output, " return self.{}(path)", fetch_method).unwrap();
|
||||
writeln!(
|
||||
output,
|
||||
" return {}self.{}(path){}",
|
||||
wrap_prefix, fetch_method, wrap_suffix
|
||||
)
|
||||
.unwrap();
|
||||
} else {
|
||||
writeln!(output, " return self.{}(path)", fetch_method).unwrap();
|
||||
writeln!(
|
||||
output,
|
||||
" return {}self.{}(path){}",
|
||||
wrap_prefix, fetch_method, wrap_suffix
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -89,11 +89,17 @@ pub fn generate_api_methods(output: &mut String, endpoints: &[Endpoint]) {
|
||||
}
|
||||
|
||||
let method_name = endpoint_to_method_name(endpoint);
|
||||
let base_return_type = endpoint
|
||||
.response_type
|
||||
.as_deref()
|
||||
.map(js_type_to_rust)
|
||||
.unwrap_or_else(|| "String".to_string());
|
||||
let base_return_type = if endpoint.returns_binary() {
|
||||
"Vec<u8>".to_string()
|
||||
} else if endpoint.returns_text() {
|
||||
// Text bodies arrive as `String`; per-type parsing is left to the caller.
|
||||
"String".to_string()
|
||||
} else {
|
||||
endpoint
|
||||
.schema_name()
|
||||
.map(js_type_to_rust)
|
||||
.unwrap_or_else(|| "String".to_string())
|
||||
};
|
||||
|
||||
let return_type = if endpoint.supports_csv {
|
||||
format!("FormatResponse<{}>", base_return_type)
|
||||
@@ -132,7 +138,9 @@ pub fn generate_api_methods(output: &mut String, endpoints: &[Endpoint]) {
|
||||
.unwrap();
|
||||
|
||||
let (path, index_arg) = build_path_template(endpoint);
|
||||
let fetch_method = if endpoint.returns_json() {
|
||||
let fetch_method = if endpoint.returns_binary() {
|
||||
"get_bytes"
|
||||
} else if endpoint.returns_json() {
|
||||
"get_json"
|
||||
} else {
|
||||
"get_text"
|
||||
|
||||
@@ -103,6 +103,14 @@ impl BrkClientBase {{
|
||||
.and_then(|mut r| r.body_mut().read_to_string())
|
||||
.map_err(|e| BrkError {{ message: e.to_string() }})
|
||||
}}
|
||||
|
||||
/// Make a GET request and return raw bytes response.
|
||||
pub fn get_bytes(&self, path: &str) -> Result<Vec<u8>> {{
|
||||
self.agent.get(&self.url(path))
|
||||
.call()
|
||||
.and_then(|mut r| r.body_mut().read_to_vec())
|
||||
.map_err(|e| BrkError {{ message: e.to_string() }})
|
||||
}}
|
||||
}}
|
||||
|
||||
/// Build series name with suffix.
|
||||
|
||||
81
crates/brk_bindgen/src/openapi/endpoint.rs
Normal file
81
crates/brk_bindgen/src/openapi/endpoint.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
use crate::openapi::{Parameter, ResponseKind};
|
||||
|
||||
/// Endpoint information extracted from OpenAPI spec.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Endpoint {
|
||||
/// HTTP method (GET, POST, etc.)
|
||||
pub method: String,
|
||||
/// Path template (e.g., "/blocks/{hash}")
|
||||
pub path: String,
|
||||
/// Operation ID (e.g., "getBlockByHash")
|
||||
pub operation_id: Option<String>,
|
||||
/// Short summary
|
||||
pub summary: Option<String>,
|
||||
/// Detailed description
|
||||
pub description: Option<String>,
|
||||
/// Path parameters
|
||||
pub path_params: Vec<Parameter>,
|
||||
/// Query parameters
|
||||
pub query_params: Vec<Parameter>,
|
||||
/// Body kind for the 200 response.
|
||||
pub response_kind: ResponseKind,
|
||||
/// Whether this endpoint is deprecated
|
||||
pub deprecated: bool,
|
||||
/// Whether this endpoint supports CSV format (text/csv content type)
|
||||
pub supports_csv: bool,
|
||||
}
|
||||
|
||||
impl Endpoint {
|
||||
/// Returns true if this endpoint should be included in client generation.
|
||||
/// Only non-deprecated GET endpoints are included.
|
||||
pub fn should_generate(&self) -> bool {
|
||||
self.method == "GET" && !self.deprecated
|
||||
}
|
||||
|
||||
/// Returns true if this endpoint returns JSON.
|
||||
pub fn returns_json(&self) -> bool {
|
||||
matches!(self.response_kind, ResponseKind::Json(_))
|
||||
}
|
||||
|
||||
/// Returns true if this endpoint returns binary data (application/octet-stream).
|
||||
pub fn returns_binary(&self) -> bool {
|
||||
matches!(self.response_kind, ResponseKind::Binary)
|
||||
}
|
||||
|
||||
/// Returns true if this endpoint returns plain text (typed or opaque).
|
||||
pub fn returns_text(&self) -> bool {
|
||||
matches!(self.response_kind, ResponseKind::Text(_))
|
||||
}
|
||||
|
||||
/// Schema name attached to the response, if any.
|
||||
pub fn schema_name(&self) -> Option<&str> {
|
||||
self.response_kind.schema_name()
|
||||
}
|
||||
|
||||
/// Returns the operation ID or generates one from the path.
|
||||
/// The returned string uses the raw case from the spec (typically camelCase).
|
||||
pub fn operation_name(&self) -> String {
|
||||
if let Some(op_id) = &self.operation_id {
|
||||
return op_id.clone();
|
||||
}
|
||||
let mut parts: Vec<String> = Vec::new();
|
||||
let mut prev_segment = "";
|
||||
|
||||
for segment in self.path.split('/').filter(|s| !s.is_empty()) {
|
||||
if segment == "api" {
|
||||
continue;
|
||||
}
|
||||
if let Some(param) = segment.strip_prefix('{').and_then(|s| s.strip_suffix('}')) {
|
||||
let prev_normalized = prev_segment.replace('-', "_");
|
||||
if !prev_normalized.ends_with(param) {
|
||||
parts.push(format!("by_{}", param));
|
||||
}
|
||||
} else {
|
||||
let normalized = segment.replace('-', "_");
|
||||
parts.push(normalized);
|
||||
prev_segment = segment;
|
||||
}
|
||||
}
|
||||
format!("get_{}", parts.join("_"))
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,13 @@
|
||||
mod endpoint;
|
||||
mod parameter;
|
||||
mod response_kind;
|
||||
mod text_schema;
|
||||
|
||||
pub use endpoint::Endpoint;
|
||||
pub use parameter::Parameter;
|
||||
pub use response_kind::ResponseKind;
|
||||
pub use text_schema::TextSchema;
|
||||
|
||||
use std::{collections::BTreeMap, io};
|
||||
|
||||
use crate::ref_to_type_name;
|
||||
@@ -11,83 +21,6 @@ use serde_json::Value;
|
||||
/// Type schema extracted from OpenAPI components
|
||||
pub type TypeSchemas = BTreeMap<String, Value>;
|
||||
|
||||
/// Endpoint information extracted from OpenAPI spec
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Endpoint {
|
||||
/// HTTP method (GET, POST, etc.)
|
||||
pub method: String,
|
||||
/// Path template (e.g., "/blocks/{hash}")
|
||||
pub path: String,
|
||||
/// Operation ID (e.g., "getBlockByHash")
|
||||
pub operation_id: Option<String>,
|
||||
/// Short summary
|
||||
pub summary: Option<String>,
|
||||
/// Detailed description
|
||||
pub description: Option<String>,
|
||||
/// Path parameters
|
||||
pub path_params: Vec<Parameter>,
|
||||
/// Query parameters
|
||||
pub query_params: Vec<Parameter>,
|
||||
/// Response type (simplified)
|
||||
pub response_type: Option<String>,
|
||||
/// Whether this endpoint is deprecated
|
||||
pub deprecated: bool,
|
||||
/// Whether this endpoint supports CSV format (text/csv content type)
|
||||
pub supports_csv: bool,
|
||||
}
|
||||
|
||||
impl Endpoint {
|
||||
/// Returns true if this endpoint should be included in client generation.
|
||||
/// Only non-deprecated GET endpoints are included.
|
||||
pub fn should_generate(&self) -> bool {
|
||||
self.method == "GET" && !self.deprecated
|
||||
}
|
||||
|
||||
/// Returns true if this endpoint returns JSON (has a response_type extracted from application/json).
|
||||
pub fn returns_json(&self) -> bool {
|
||||
self.response_type.is_some()
|
||||
}
|
||||
|
||||
/// Returns the operation ID or generates one from the path.
|
||||
/// The returned string uses the raw case from the spec (typically camelCase).
|
||||
pub fn operation_name(&self) -> String {
|
||||
if let Some(op_id) = &self.operation_id {
|
||||
return op_id.clone();
|
||||
}
|
||||
// Generate from path: /api/block/{hash} -> "get_block"
|
||||
// Skip "api" prefix, convert hyphens to underscores, avoid redundant param names
|
||||
let mut parts: Vec<String> = Vec::new();
|
||||
let mut prev_segment = "";
|
||||
|
||||
for segment in self.path.split('/').filter(|s| !s.is_empty()) {
|
||||
if segment == "api" {
|
||||
continue;
|
||||
}
|
||||
if let Some(param) = segment.strip_prefix('{').and_then(|s| s.strip_suffix('}')) {
|
||||
// Only add "by_{param}" if the previous segment doesn't already contain the param name
|
||||
let prev_normalized = prev_segment.replace('-', "_");
|
||||
if !prev_normalized.ends_with(param) {
|
||||
parts.push(format!("by_{}", param));
|
||||
}
|
||||
} else {
|
||||
let normalized = segment.replace('-', "_");
|
||||
parts.push(normalized);
|
||||
prev_segment = segment;
|
||||
}
|
||||
}
|
||||
format!("get_{}", parts.join("_"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Parameter information
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Parameter {
|
||||
pub name: String,
|
||||
pub required: bool,
|
||||
pub param_type: String,
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
/// Parse OpenAPI spec from JSON string
|
||||
///
|
||||
/// Pre-processes the JSON to handle oas3 limitations:
|
||||
@@ -164,7 +97,7 @@ pub fn extract_endpoints(spec: &Spec) -> Vec<Endpoint> {
|
||||
|
||||
for (path, path_item) in paths {
|
||||
for (method, operation) in get_operations(path_item) {
|
||||
if let Some(endpoint) = extract_endpoint(path, method, operation) {
|
||||
if let Some(endpoint) = extract_endpoint(path, method, operation, spec) {
|
||||
endpoints.push(endpoint);
|
||||
}
|
||||
}
|
||||
@@ -186,11 +119,16 @@ fn get_operations(path_item: &PathItem) -> Vec<(&'static str, &Operation)> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn extract_endpoint(path: &str, method: &str, operation: &Operation) -> Option<Endpoint> {
|
||||
fn extract_endpoint(
|
||||
path: &str,
|
||||
method: &str,
|
||||
operation: &Operation,
|
||||
spec: &Spec,
|
||||
) -> Option<Endpoint> {
|
||||
let path_params = extract_path_parameters(path, operation);
|
||||
let query_params = extract_parameters(operation, ParameterIn::Query);
|
||||
|
||||
let response_type = extract_response_type(operation);
|
||||
let response_kind = extract_response_kind(operation, spec);
|
||||
let supports_csv = check_csv_support(operation);
|
||||
|
||||
Some(Endpoint {
|
||||
@@ -201,7 +139,7 @@ fn extract_endpoint(path: &str, method: &str, operation: &Operation) -> Option<E
|
||||
description: operation.description.clone(),
|
||||
path_params,
|
||||
query_params,
|
||||
response_type,
|
||||
response_kind,
|
||||
deprecated: operation.deprecated.unwrap_or(false),
|
||||
supports_csv,
|
||||
})
|
||||
@@ -272,28 +210,59 @@ fn extract_parameters(operation: &Operation, location: ParameterIn) -> Vec<Param
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn extract_response_type(operation: &Operation) -> Option<String> {
|
||||
let responses = operation.responses.as_ref()?;
|
||||
fn extract_response_kind(operation: &Operation, spec: &Spec) -> ResponseKind {
|
||||
let response = operation
|
||||
.responses
|
||||
.as_ref()
|
||||
.and_then(|r| r.get("200"))
|
||||
.and_then(|r| match r {
|
||||
ObjectOrReference::Object(o) => Some(o),
|
||||
ObjectOrReference::Ref { .. } => None,
|
||||
});
|
||||
let Some(response) = response else {
|
||||
return ResponseKind::Text(None);
|
||||
};
|
||||
|
||||
// Look for 200 OK response
|
||||
let response = responses.get("200")?;
|
||||
|
||||
match response {
|
||||
ObjectOrReference::Object(response) => {
|
||||
// Look for JSON content
|
||||
let content = response.content.get("application/json")?;
|
||||
|
||||
match &content.schema {
|
||||
Some(ObjectOrReference::Ref { ref_path, .. }) => {
|
||||
// Extract type name from reference like "#/components/schemas/Block"
|
||||
Some(ref_to_type_name(ref_path)?.to_string())
|
||||
}
|
||||
Some(ObjectOrReference::Object(schema)) => schema_to_type_name(schema),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
ObjectOrReference::Ref { .. } => None,
|
||||
if response.content.contains_key("application/octet-stream") {
|
||||
return ResponseKind::Binary;
|
||||
}
|
||||
if let Some(content) = response.content.get("application/json") {
|
||||
return ResponseKind::Json(
|
||||
schema_name_from_content(content).unwrap_or_else(|| "*".to_string()),
|
||||
);
|
||||
}
|
||||
if let Some(content) = response.content.get("text/plain; charset=utf-8") {
|
||||
let schema = schema_name_from_content(content).map(|name| {
|
||||
let is_numeric = is_numeric_schema(spec, &name);
|
||||
TextSchema { name, is_numeric }
|
||||
});
|
||||
return ResponseKind::Text(schema);
|
||||
}
|
||||
ResponseKind::Text(None)
|
||||
}
|
||||
|
||||
fn schema_name_from_content(content: &oas3::spec::MediaType) -> Option<String> {
|
||||
match content.schema.as_ref()? {
|
||||
ObjectOrReference::Ref { ref_path, .. } => {
|
||||
Some(ref_to_type_name(ref_path)?.to_string())
|
||||
}
|
||||
ObjectOrReference::Object(schema) => schema_to_type_name(schema),
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolves `name` against `components.schemas` and reports whether the
|
||||
/// underlying primitive is `integer` or `number`.
|
||||
fn is_numeric_schema(spec: &Spec, name: &str) -> bool {
|
||||
let Some(components) = spec.components.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
let Some(ObjectOrReference::Object(schema)) = components.schemas.get(name) else {
|
||||
return false;
|
||||
};
|
||||
matches!(
|
||||
schema.schema_type.as_ref(),
|
||||
Some(SchemaTypeSet::Single(SchemaType::Integer | SchemaType::Number))
|
||||
)
|
||||
}
|
||||
|
||||
fn schema_type_from_schema(schema: &Schema) -> Option<String> {
|
||||
8
crates/brk_bindgen/src/openapi/parameter.rs
Normal file
8
crates/brk_bindgen/src/openapi/parameter.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
/// Parameter information.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Parameter {
|
||||
pub name: String,
|
||||
pub required: bool,
|
||||
pub param_type: String,
|
||||
pub description: Option<String>,
|
||||
}
|
||||
29
crates/brk_bindgen/src/openapi/response_kind.rs
Normal file
29
crates/brk_bindgen/src/openapi/response_kind.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use crate::openapi::TextSchema;
|
||||
|
||||
/// 200-response body shape.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ResponseKind {
|
||||
/// JSON body, schema named (e.g. "Block").
|
||||
Json(String),
|
||||
/// `text/plain` body. `Some(schema)` carries a typed shape (e.g. "Height", "Hex");
|
||||
/// `None` is the escape hatch for opaque text.
|
||||
Text(Option<TextSchema>),
|
||||
/// `application/octet-stream`.
|
||||
Binary,
|
||||
}
|
||||
|
||||
impl ResponseKind {
|
||||
/// Schema name, if the body is named (Json or typed Text).
|
||||
pub fn schema_name(&self) -> Option<&str> {
|
||||
match self {
|
||||
Self::Json(s) => Some(s.as_str()),
|
||||
Self::Text(Some(t)) => Some(t.name.as_str()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// True when a typed text body needs numeric parsing (`int(...)` etc.).
|
||||
pub fn text_is_numeric(&self) -> bool {
|
||||
matches!(self, Self::Text(Some(t)) if t.is_numeric)
|
||||
}
|
||||
}
|
||||
8
crates/brk_bindgen/src/openapi/text_schema.rs
Normal file
8
crates/brk_bindgen/src/openapi/text_schema.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
/// Schema metadata for a typed `text/plain` response.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TextSchema {
|
||||
/// Schema name, e.g. "Height", "Hex".
|
||||
pub name: String,
|
||||
/// True when the underlying primitive is `integer`/`number` (body needs numeric parsing).
|
||||
pub is_numeric: bool,
|
||||
}
|
||||
Reference in New Issue
Block a user