MissingPointerError: Token "AppError" does not exist in example
marclave opened this issue · 8 comments
great library! I noticed for the example swagger spec, it renders the spec below. However, the AppError
response is undefined in the rendering of Redocly + Swagger UI since AppError doesnt exist inside of the Schemas for the OAS spec
there's also more errors on https://editor.swagger.io
openapi: 3.1.0
info:
title: Aide axum Open API
summary: An example Todo application
description: |
# Todo API
A very simple Todo server with documentation.
The purpose is to showcase the documentation workflow of Aide rather
than a correct implementation.
version: ''
paths:
/todo/:
get:
description: List all Todo items.
responses:
'200':
description: ''
content:
application/json:
schema:
$ref: '#/components/schemas/TodoList'
default:
description: ''
content:
application/json:
schema:
$ref: '#/components/schemas/AppError'
example:
error: some error happened
error_id: 00000000-0000-0000-0000-000000000000
post:
description: Create a new incomplete Todo item.
requestBody:
description: New Todo details.
content:
application/json:
schema:
$ref: '#/components/schemas/NewTodo'
required: true
responses:
'201':
description: ''
content:
application/json:
schema:
$ref: '#/components/schemas/TodoCreated'
default:
description: ''
content:
application/json:
schema:
$ref: '#/components/schemas/AppError'
example:
error: some error happened
error_id: 00000000-0000-0000-0000-000000000000
/todo/{id}:
get:
description: Get a single Todo item.
parameters:
- in: path
name: id
description: The ID of the Todo.
required: true
schema:
description: The ID of the Todo.
type: string
format: uuid
style: simple
responses:
'200':
description: ''
content:
application/json:
schema:
$ref: '#/components/schemas/TodoItem'
example:
complete: false
description: fix bugs
id: 00000000-0000-0000-0000-000000000000
'404':
description: todo was not found
default:
description: ''
content:
application/json:
schema:
$ref: '#/components/schemas/AppError'
example:
error: some error happened
error_id: 00000000-0000-0000-0000-000000000000
delete:
description: Delete a Todo item.
parameters:
- in: path
name: id
description: The ID of the Todo.
required: true
schema:
description: The ID of the Todo.
type: string
format: uuid
style: simple
responses:
'204':
description: The Todo has been deleted.
'404':
description: The todo was not found
default:
description: ''
content:
application/json:
schema:
$ref: '#/components/schemas/AppError'
example:
error: some error happened
error_id: 00000000-0000-0000-0000-000000000000
/todo/{id}/complete:
put:
description: Complete a Todo.
parameters:
- in: path
name: id
description: The ID of the Todo.
required: true
schema:
description: The ID of the Todo.
type: string
format: uuid
style: simple
responses:
'204':
description: no content
default:
description: ''
content:
application/json:
schema:
$ref: '#/components/schemas/AppError'
example:
error: some error happened
error_id: 00000000-0000-0000-0000-000000000000
/docs/:
get:
description: This documentation page.
responses:
'200':
description: HTML content
content:
text/html:
schema:
type: string
default:
description: ''
content:
application/json:
schema:
$ref: '#/components/schemas/AppError'
example:
error: some error happened
error_id: 00000000-0000-0000-0000-000000000000
security:
- ApiKey: []
components:
securitySchemes:
ApiKey:
type: apiKey
in: header
name: X-Auth-Key
description: A key that is ignored.
schemas:
NewTodo:
description: New Todo details.
type: object
required:
- description
properties:
description:
description: The description for the new Todo.
type: string
SelectTodo:
type: object
required:
- id
properties:
id:
description: The ID of the Todo.
type: string
format: uuid
TodoCreated:
description: New Todo details.
type: object
required:
- id
properties:
id:
description: The ID of the new Todo.
type: string
format: uuid
TodoItem:
description: A single Todo item.
type: object
required:
- complete
- description
- id
properties:
complete:
description: Whether the item was completed.
type: boolean
description:
description: The description of the item.
type: string
id:
type: string
format: uuid
TodoList:
type: object
required:
- todo_ids
properties:
todo_ids:
type: array
items:
type: string
format: uuid
tags:
- name: todo
description: Todo Management
The errors at openapi
and info
, might be because the client does not support this version. If you click on the Try our new Editor
button on their website, these two errors would not appear. This is also true for this error:
Structural error at paths./todo/search/{query}.get.parameters.1.schema.type
should be string
The other errors related to AppError
not being included in components
structure, seem to show a bug with default_response_with
?
The errors at
openapi
andinfo
, might be because the client does not support this version. If you click on theTry our new Editor
button on their website, these two errors would not appear. This is also true for this error:Structural error at paths./todo/search/{query}.get.parameters.1.schema.type should be string
The other errors related to
AppError
not being included incomponents
structure, seem to show a bug withdefault_response_with
?
Yeah I think it's a bug with default_response_with since it doesn't get rendered in the schemas :)
@marclave Quick fix for now:
- implement
OperationOutput
forAppError
impl aide::OperationOutput for AppError {
type Inner = Self;
fn operation_response(
ctx: &mut aide::gen::GenContext,
operation: &mut aide::openapi::Operation,
) -> Option<aide::openapi::Response> {
<axum::Json<AppError> as aide::OperationOutput>::operation_response(ctx, operation)
}
fn inferred_responses(
ctx: &mut aide::gen::GenContext,
operation: &mut aide::openapi::Operation,
) -> Vec<(Option<u16>, aide::openapi::Response)> {
if let Some(res) = Self::operation_response(ctx, operation) {
vec![(None, res)]
} else {
Vec::new()
}
}
}
Note: vec![(None, res)]
, the None
here means status code can be any status code, in other words, a default
response.
- In
main
useaide::gen::all_error_responses(true)
- Use custom extractors, with axum_macros like so:
#[derive(FromRequestParts, OperationIo)]
#[from_request(via(axum::extract::Path), rejection(AppError))]
#[aide(
input_with = "axum::extract::Path<T>",
// output_with = "axum::extract::Path<T>", // not needed for path
json_schema
)]
pub struct Path<T>(pub T);
- Use Results on your routes like so:
async fn my_route(Path(input): Path<MyRouteInput>) -> impl IntoApiResponse {
// you can retrun Ok(axum::Json(YourType));
// YourType must implement `OperationOutput` if not using `axum::Json`
Return Err(AppError::new("error"));
// ofc you use other error types with their own `OperationOutput` implementation
}
Note axum::Json
is used. I don't see a value in using the Json
, in the example, as an output, but it is definitely useful as an input.
Amazing! Thanks so much
I am not sure if there is a reason default_response_with
doesn't add a schema in components. Is it intentional?
An ugly modification of default_response_with
, could be a breaking change:
pub fn default_response_with<R, F>(mut self, transform: F) -> Self
where
R: OperationOutput + JsonSchema,
F: Fn(TransformResponse<R::Inner>) -> TransformResponse<R::Inner> + Clone,
{
if let Some(p) = &mut self.api.paths {
for (_, p) in &mut p.paths {
let p = match p {
ReferenceOr::Reference { .. } => continue,
ReferenceOr::Item(p) => p,
};
for (_, op) in iter_operations_mut(p) {
let _ = TransformOperation::new(op)
.default_response_with::<R, F>(transform.clone());
}
}
}
if let Some(c) = &mut self.inner_mut().components {
let mut schema = in_context(|ctx| {
// ctx.schema.subschema_for::<R>()
ctx.schema.root_schema_for::<R>()
});
let name = schema.schema.metadata().title.as_ref().unwrap().clone();
// let name = type_name::<R>().to_owned();
c.schemas.insert(
name,
crate::openapi::SchemaObject {
json_schema: schemars::schema::Schema::Object(schema.schema),
example: None,
external_docs: None,
},
);
}
self
}
Honestly, not a big fan of TransformOperation
and friends, they add so much confusion. The OperationOutput
and OperationInput
traits way of doing things make much more sense to me.
Edited. it is indeed not the examples responsibility to set the schema, will post a fix tomorrow.
if extract shema is set to false in the example it works. The bug comes from Jsonschema that knows it must extract the schema and it thinks it is already being extracted somewhere else and thus just places the rest.
Well, turns out it was in finish_api_with
, the generated paths were necessary for applying the default responses, but the schema extraction and context reset happened before the trasform function was applied.
Fix is in master.